You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit f7bfa66
Browse filesBrowse the repository at this point in the historyBrowse files
feat(doctor): judge a solution's permissions, and report a shell with no app (#1221)
* feat(doctor): judge a solution's permissions, by the reader that owns them
The missing-permission class — a role promising authority nothing backs —
is already caught by core: reading the contract through modulecontract
.Load refuses "role installer grants documents:read, which is neither
within the module's own scope ceilings nor a scope one of its bindings
spells out". Nothing was running that over solutions, so it was found at
the first refused call instead of before install. It runs here now, and
a test pins the message.
Adds consumed_module_without_binding as a WARNING: a module in
api.consumes the contract declares no binding for. Correct when the
module is reached with the viewer's own credential — wiki reaches robin,
tracing and annotations that way and says so — and a bug when the
solution calls it on a person's behalf. The two are indistinguishable
from these documents, so it is surfaced rather than failed.
A first draft also failed any binding no role named by id. It reported
wiki and proposals — the two longest-running solutions — as broken,
which is how the second mechanism was found: a role reaches another
module's authority by declaring that module's resource KINDS, with no
RoleBinding at all. That check is gone, and the source says what cannot
be answered from a directory: the authoritative version needs
Contract.Resolve(values) against a composition.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(doctor): report a page shell declared with no app behind it
Conforming the fleet to one shape added the `web` shell's MANIFEST to
solutions whose page had never been written — writing one would have been
inventing product in a conforming change. lastlogin-go and robin-nest are
in that state on main today: correctly shaped, and `codefly run service
web` has nothing to start.
A declared service with no source is invisible to every other reading.
Each manifest is valid, every gate passes, and the first sign is a run
that does nothing. So it is reported — as a warning, because the solution
is not wrong and nothing breaks until someone runs the shell.
wiki, proposals and robin-builder ship real shells and are silent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/commands.md
+17Lines changed: 17 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2465,13 +2465,30 @@ Every rule is one that silently broke something. The two worth knowing:
2465
2465
|||
2466
2466
|---|---|
2467
2467
|`owner_aware_missing`| The authority issues a person a capability at a solution **only** when its module contract declares `owner_aware: true`, and refuses by name otherwise. Without it the solution receives no owner-selected scope and can act for nobody — and nothing fails at build time to tell you. |
2468
+
|`consumed_module_without_binding`| A module named in `api.consumes` that the contract declares no binding for. A **warning**: correct when the module is reached with the viewer's own credential (wiki reaches robin, tracing and annotations that way), a bug when the solution calls it on a person's behalf — and the two look identical until a call is refused. |
2468
2469
|`module_edge_declared`| A solution reaches every module through the **host's door** — a saas host's gateway and frontend, or the authority's hop beside the authority interface. A `service-dependencies` entry naming anything else renders into a mesh allow rule straight to that module's service, past the authority's check. |
2469
2470
2470
2471
Exit codes: `0` every judged solution conforms (warnings allowed), `1` at least
2471
2472
one check failed. `--json` prints a versioned report per module
2472
2473
(`{schema_version, module, dir, is_solution, status, checks[]}`), so a
2473
2474
consumer repository can gate on it instead of hand-rolling the equivalent.
2474
2475
2476
+
**Permissions.** The contract is read through Core's own loader, which refuses
2477
+
a role granting a scope no binding backs and no ceiling covers — *"role
2478
+
`installer` grants `documents:read`, which is neither within the module's own
2479
+
scope ceilings nor a scope one of its bindings spells out"*. That is the
2480
+
missing-permission class, caught before install rather than at the first
2481
+
refused call. What a composition's values resolve each binding to is not
2482
+
judged: that needs the composition, not the directory.
2483
+
2484
+
`page_shell_has_no_app` is worth knowing about: conforming the fleet to one
2485
+
shape added the `web` shell's *manifest* to solutions whose page had never been
2486
+
written, because writing one would have been inventing product in a conforming
2487
+
change. Those solutions are correctly shaped and `codefly run service web` has
2488
+
nothing to start on them. A declared service with no source is otherwise
2489
+
invisible — every manifest reads fine, and the first sign is a run that does
2490
+
nothing.
2491
+
2475
2492
A warning is not a failure: it names something worth seeing that does not make
2476
2493
the solution wrong — publishing the page's shell, a headless solution with no
2477
2494
page declaration, a solution with no shell at all.
r.ok(CodeConsumedWithoutBinding, "every consumed module has a binding")
517
+
return
518
+
}
519
+
r.warn(CodeConsumedWithoutBinding,
520
+
fmt.Sprintf("consumed with no binding in the contract: %s", strings.Join(unbound, ", ")),
521
+
"correct when the module is reached with the VIEWER'S own credential or on nobody's behalf; a bug when the solution calls it on a person's behalf, and then the call is refused at runtime with nothing failing earlier. Confirm each one")
522
+
}
523
+
524
+
// checkPageShellHasAnApp reports a shell that is declared and has no app
525
+
// behind it — a service.codefly.yaml naming a Next.js service with no
526
+
// package.json beside it.
527
+
//
528
+
// It is a real state and not a hypothetical: conforming the fleet to one shape
529
+
// added the shell's MANIFEST to several solutions whose page had never been
530
+
// written, because writing one would have been inventing product in a
531
+
// conforming change. The declaration is right and the gap is real, so
532
+
// `codefly run service web` has nothing to start on those.
533
+
//
534
+
// A warning, not a failure: the solution is correctly shaped, and nothing is
535
+
// broken until someone runs the shell. It is reported because a declared
536
+
// service with no source is otherwise invisible — every manifest reads fine,
r.ok(CodePageShellHasNoApp, "the page's shell has an app behind it")
548
+
return
549
+
}
550
+
}
551
+
r.warn(CodePageShellHasNoApp,
552
+
fmt.Sprintf("the %q shell is declared with no app behind it", PageService),
553
+
"`codefly run service "+PageService+"` has nothing to start. Write the shell, or drop the service from module.codefly.yaml — a declaration with no source is the one state neither reading the manifests nor running the gates reveals")
0 commit comments