Repository navigation
feat(doctor): check a solution against the one shape it has - #1218
Conversation
`codefly doctor solution [module]` judges a solution against the shape `codefly add solution` generates, read-only, with stable diagnostic codes and a versioned `--json` report. The shape was previously checkable only against somebody's survey of the solutions that happened to exist, which is how obin-ai/core-solutions reached nine solutions in four different states — two of them unable to receive a person capability at all, with nothing failing to say so. A shape nothing can check is a convention, and a convention drifts. Every rule is one that silently broke something. The two that cost the most: a contract without `owner_aware: true`, which the authority refuses by name so the solution can act for nobody; and a `service-dependencies` entry naming a module, which renders a mesh allow rule past the authority's check. Both host doors are known, as core-solutions' own check_solution_mesh.py holds them: a saas host's gateway and frontend unconditionally, and the authority's hop only in a manifest that also requires the authority interface. Running the first draft against the real solutions is what caught that — it flagged lastlogin-go's legitimate saas door. Verified against all nine solutions in obin-ai/core-solutions: wiki and proposals conform; lastlogin-go, releases, summary, robin-nest and lastlogin-python fail, reproducing the gap ledger in obin-ai/core-solutions#466 that was derived by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Running the check across the real tree found two gaps in it. `--dir` judges a module directory on its own. A consumer repository gating `solutions/*/` has module directories and not necessarily a workspace at each one: core-solutions' excel-extract is a real solution with no workspace.codefly.yaml, which made it the single solution the check could not judge at all. `--require-solution` closes what --dir leaves open. A directory with no solution manifest reads as "not a solution" and passes — right for a library in the tree, wrong for a solution whose manifest is missing, which is exactly robin-builder's state. A gate walking solutions/*/ means "every one of these is a solution", and now says so. In --dir mode the no-solution-found guard does not apply: the caller named the directory, so "it is not a solution" is an answer rather than a vacuous pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Pushed Using it on the six conforming PRs — it found three real gaps
Both failures are the same real defect: the PR adds That is three PRs that would have merged looking conformant. It is the argument for this command. Two gaps the tree run found in the command itself
In The gate, on core-solutions main todayfor s in solutions/*/; do codefly doctor solution --dir "$s" --require-solution || exit 1; done( Tests, lint and the regenerated reference are all green. 🤖 Generated with Claude Code |
Follow-up to #1216, which added
codefly add solution. That verb generates the shape; nothing checked it.codefly doctor solution [module]judges a solution against the shapeadd solutiongenerates — read-only, stable diagnostic codes, versioned--jsonreport. With no argument it judges every module in the workspace; a module shipping no solution manifest is reported as "not a solution" rather than as broken.Classification: fix
A gap in the CLI, filled in the CLI. No core change.
Why
The shape was checkable only against somebody's survey of the solutions that happened to exist. That is how
obin-ai/core-solutionsreached nine solutions in four different states, two of them unable to receive a person capability at all with nothing failing to say so. A shape nothing can check is a convention, and a convention drifts.Epic obin-ai/core-solutions#466's "done when" is "every solution's documents match what
codefly add solutiongenerates" — until now verifiable only by eyeball.Reuse
Every document is read by the reader that already owns it —
manifest.LoadConsumption,modulecontract.Load,composition.LoadPackageManifest,solutionhost.ParseFrontend,resources.LoadModuleFromDir. The checker adds no parsing of its own, so a rule it reports is a rule those readers enforce.The command follows
doctor workspaceexactly:SilenceUsage, stable codes documented inLong,--json, exit 0/1.Every rule is one that silently broke something
Two carry the weight:
owner_aware_missingowner_aware: true, refusing by name otherwise. Without it the solution can act for nobody — and nothing fails at build time.module_edge_declaredservice-dependenciesentry naming anything but the host's door renders a mesh allow rule straight to that module's service, past the authority's check.A warning is not a failure: publishing the page's shell, a headless solution, a solution with no shell. Those are worth seeing and do not make the solution wrong.
Both host doors, because running it caught that I only knew one
The first draft flagged
saas/frontendandsaas/auth-gatewayon lastlogin-go as illegal edges. They are not — core-solutions' owncheck_solution_mesh.pyholds two doors:A saas host's gateway and frontend unconditionally; the authority's hop only in a manifest that also requires the authority interface, since without it an edge to the hop is a path to a module service like any other. The checker now holds the same table. Shipping the first draft would have told all seven conforming PRs to delete their legitimate door.
Verification
Run against all nine real solutions in
obin-ai/core-solutions:That independently reproduces the gap ledger in #466, which was derived by hand — and confirms
wikiandproposalsas two independent instances of the shape rather than one reading of one file.TestTheGeneratedShapeConformspins the loop closed: whatadd solutiongenerates passesdoctor solution. If they ever disagree, one is wrong and a scaffolded solution is born failing.go build ./...,go test ./pkg/solutionshape ./cmdpass,golangci-lint run cmd/... pkg/solutionshape/...0 issues,docs/cli-reference.mdregenerated.What I did not verify
solution-runtime-gopin, the boundary denylist, removed credential keys. Those stay with the consumer repository.excel-extractandrobin-builderwere not judged: neither is present in the checkout I had.MCP
Not exposed. It is a read-only diagnostic a person runs or a CI gate calls;
doctor workspaceis not exposed either.Docs
docs/commands.md(the verb, both load-bearing rules, exit codes,--jsonshape),docs/cli-reference.md(regenerated),AGENTS.md(How-To Index).🤖 Generated with Claude Code