Repository navigation
fix(run): address the #1128 review findings - #1158
Conversation
…ost implements it The launcher delivered a module's manifest by posting to one host's private routes and writing its envelope through that host's gateway, so a run could deliver to that host and to nothing else. When the run's host service declares, in its module's interface endpoints, that it implements codefly.dev/composition, the launcher now carries the signed manifest and the module's delivery credential to ApplyManifest over the host's endpoint, reads the applied state from the synchronous answer, writes no envelope (the manifest is the ceiling there) and requires no gateway. The native deliverer gains two optional hooks, Post and Applied; the HTTP delivery remains for a host that does not implement the interface. Refusals map onto the statuses the run already classifies. Fix at the owner: the CLI knows Codefly interfaces, never a host's routes. Not released; proven with a local build against codefly-dev/authority. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With each manifest delivered through codefly.dev/composition, the run now delivers the authority's own document beside it: every method the module's published protobuf contracts serve, with what each declares with the authority.hop.v1.policy option, read from the module package (each contract refused unless its bytes are the digest the package names). The document names the core manifest's module, generation and digest, and is signed in the same envelope format by the run's release key, through the authority's delivery library. A module with no package declares no method policy and sends none. The CLI learns no host route and no policy vocabulary of its own: it reads the authority's option and speaks the authority's composition interface, both from codefly-dev/authority at bc4d129 (authority client, delivery and hop libraries). Proven live with codefly-dev/authority's console: the delivery is applied, the authority's Check admits Maya's WhoAmI and Version through the hop from the delivered policy, answers an unserved method NotFound, and refuses a method check for a module no signed document described. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ither --exclude-workspace-configuration trimmed a group from resolution but not from the groups the CLI renders itself for a native run (module-authority). A run excluding module-authority still rendered it, and refused to start when the environment's host names no gateway, which is every run against the authority interface rather than the saas host. The render now sees the service's declared groups less the excluded ones; the declared service is not mutated. Test: an excluded group is not handed to the renderer; a run excluding nothing passes the service through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core's manifest names a module's own resource kinds without their actions, so
an authority receiving it can only serve each kind with any action, and a
person's ask for everything they hold there cannot be enumerated: the runtime's
playground refused every task ("declares runtime.tasks without its actions").
The authority's document delivered with each manifest now carries the module's
scope ceilings, read from its module contract (gitops.ScopeCeilings), beside the
method policies; the document is sent when the module declares either. The
field is the composition interface's (codefly-dev/authority 064127b), which the
authority client moves to.
Tests: the document carries the ceilings (sabotage-checked), and ceilings are
read from a contract kind by kind, none without one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…OpenAPI contract
The authority now admits an HTTP request by its route ("<VERB> <template>",
codefly-dev/authority 801bf7a), so the delivery reads a module's published
OpenAPI contracts as it reads its protobuf ones: each operation is the route
"<VERB> <path>" and its x-codefly-authority-policy is the same
authority.hop.v1.Policy a proto method marks itself with (decoded with
protojson). An unmarked operation is listed with nothing set, so the authority
refuses it by name; an extension that is not a policy refuses the delivery.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ocument The export decided whether a rest endpoint carries a contract by asking the module's exported endpoint, which is read from the interface and carries no API details, so every REST face was skipped as "no OpenAPI document" — a go-grpc service's openapi/api.swagger.json and a FastAPI service's alike. It now asks the service for the file core loads a REST contract from (standards.OpenAPIPath), as it already asks for proto/buf.yaml; a REST face whose service carries none is still skipped as reachability only. Found publishing the model gateway's route policies (its OpenAPI document). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps main's core (v0.19.7 pre-release) with this branch's authority libraries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An environment declares the secrets a cell's store holds as random values under service-secrets.generate, which codefly deploy secrets honours; a local run ignored the same declaration, so every playground generated its secrets from a make target or script calling codefly config generate before it ran. A run now generates, before it loads configuration, every key its environment's generators declare that the workspace's secret configuration does not hold yet: the same generator (EnvironmentSecretGenerator.Encoding, moved from deploysecrets), into the file codefly config generate writes, never rotating a held value and never printing one. A service-scope generator names its services; a local run guesses none. An environment run locally names no store, so service-secrets may now declare generators alone (Stored reports whether it names one). A block with neither is refused, and a render or deploy secrets refuses an environment that names no store, since a render carries secrets only as references into one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es for them A module contract may name, per operation, the audience's methods a binding calls instead of a ceiling (core 99d67976, contracts/module calls). The render resolves each contract with the composed callees' own method policies, read from their published contracts as delivery already reads them (MethodPolicies): the operation's ceiling is what the calls require, so the delivered manifest carries plain kinds to whichever host answers, and the caller names none of the callee's kinds. A call the callee does not serve, one it serves declaring nothing, and a callee this workspace does not compose are each refused by name. The module-contract kit's new fixture renders here against two composed callees (a protobuf and an OpenAPI contract), and its ceilings are asserted. Core moves to 99d67976 (its 257e6b23 plus calls). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cies A module declares the methods it serves from a protocol it does not own in its module contract (core a57ef6ce, foreign_methods); MethodPolicies now delivers them beside the methods its published contracts declare, so the authority decides a call to an OpenTelemetry Export at the module like any other, and a caller's binding may name it among its calls. A foreign method a published contract also serves is refused. Core moves to a57ef6ce. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…assistant module The module-contract kit renders through this CLI again: c18e661 shipped with the kit's foreign-methods fixture naming a principal the render refuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
grpcio, which the *_pb2_grpc stubs check at import against the grpc_tools the companion pins (1.71.0); on the proto-sources path, the shared modules the contract imports (googleapis-common-protos), as the descriptor-set path already did. A dependency counts as declared by its distribution name, so a developer's own pin is kept. buf/validate maps to no distribution: none on PyPI ships it, and the companion (core) now drops an import used only for an option. Core pinned to the companion 0.0.17 commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A method bound with google.api.http is listed under each route, "<VERB> <template>", with the method's own policy, so the hop's HTTP face decides a REST call to a proto-declared module by its route without a second contract. The proto is the one place a module declares both. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
"{name=a/*}" is listed as "a/{name}", "{name=*}" as "{name}" and "{name=**}"
stays, so the authority matches segments and never reads a variable's
sub-template.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he protos
`--from module/service/endpoint` copied the endpoint's declared Package,
which is empty for every service that declares none in service.codefly.yaml,
and the library's contract was then refused ("package is required"). The
package is settled the way `generate contracts` settles it: the one the
service's own protos declare their services in.
Proven on obin-ai/module-evaluation (registry/grpc declares no package):
go, typescript and python clients generated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The catalog's routes[].path joined the group's path and the route's, which core already holds to start with it: every REST catalog read "/v1/x/y/v1/x/y". One helper lists the routes for both `generate contracts` and `generate client`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A Connect endpoint serves the service's protobuf contract over the Connect protocol family, so `--from module/service/connect` selects it and builds the same descriptor set a grpc endpoint does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he loader does Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tor path Compiled as sources, a vendored copy of codefly/base or authority/hop under the service's own proto tree was emitted again as the library's own bindings; the descriptor path marks it as an import with core's rule (core 95d56f5f: those namespaces are foreign to a Go library). Proven on the document store: its Go client now carries gen/documents alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A `kind: solution` could not be composed through a native run on an authority that implements codefly.dev/composition: nothing read the solution's roles, so the authority's document carried none and the host refused the solution for declaring no installer. - A solution declares its own kinds and its roles in `roles.codefly.yaml` beside its solution manifest (schema codefly/solution-roles/v1): the kinds it serves under `scope_ceilings`, in the module contract's spelling, and `roles`, each a name and the scopes it grants within those kinds, one of them `installer`. Neither existing file could carry them: core's module contract has scope ceilings and no roles, and a composed solution asks for authority from its solution manifest, not a contract; core's solution manifest's `permissions` are what a solution requires, not what it grants a person; the host's `permissions.codefly.yaml` is one host's vocabulary contribution with no roles. The declaration folds into core's module contract when core's full manifest carries roles. - `SolutionRoles` reads it strictly and refuses by name, naming the file: no installer, no roles, no kinds, a role beyond the kinds' actions or on an undeclared kind, a role named twice, granting nothing or not a name, a kind named twice or with no action, another schema, an unknown key. - The render names the declared kinds in the core manifest's authority block (the authority's document ceils and grants roles only on kinds the core manifest names); the delivery carries the kinds as scope ceilings and the roles in `AuthorityManifestV1.roles` (authority 39f45e9d); the launcher wires the reader beside MethodPolicies and ScopeCeilings. Tests: the declaration is read as written and refused by rule; the signed document carries the roles; a scoped and an unscoped solution name their kinds in the manifest and a broken declaration fails the render. go test ./pkg/gitops/ ./cmd/run/ green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…l endpoint --naming-scope resolves every address in the scope the run uses, with `codefly run`'s rule: omitted keeps the environment's declared scope, an explicit empty value clears it. Local addresses now come from common.ResolvePreferredNative, so a live run of that scope answers with its concrete allocation and the static hash stays the offline fallback. --call-endpoint <module>/<service>/<endpoint> names a declared HTTP-based endpoint (parsed by core's ParseEndpointReference, loaded from the workspace) every prepared call is sent to instead of the owner's listener: the composition's hop. It is applied only after the owner's served Connect surface and published contract are verified, and moves only call.address; operation, procedure or REST route, contract, digest and policy stay the owner's. EncodePrepared/VerifyPrepared are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The default output was <module>/pkg/gen/permissions, outside every Go module, so no service could import it without --output. Each Go service now gets its own table at <service>/<source-dir>/pkg/gen/permissions: source-dir is core's Go agent setting (golang.GoAgentSettings, "code" by default), its module root from golang.SplitSourceDir is where go.mod lives, and pkg/gen is the tree the go-grpc layout generates its bindings into. A service's table holds the methods its own published contracts serve (gitops.ServiceMethodPolicies, which MethodPolicies is now built on) plus the module contract's foreign methods, which name no service. TypeScript stays beside the Go file: no generator places TypeScript per service. A service that publishes a contract with no go.mod at its Go source root fails by name. --output still writes the whole module's table to one directory; --check with no flags checks every default destination. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h a warning
A run of fewer services than the composition refused a declared group's
${endpoint:...} reference to a service the workspace composes but the run
does not contain, reported as "the producer is not a service of this
workspace". A module-closure run's graph holds the seeds' modules and the
modules their service dependencies reach, never a module only a configuration
value names, and the plan gate judged declared groups against that graph
alone. Reproduced on platform-obin: `run service authority/hop` alone
(local-authority) refused hop/UPSTREAM_* ten times, and `run service
wiki/backend` (local) refused mcp/issuer-url -> saas/frontend.
The gate now judges a declared group against the plan's graph and then the
rest of the workspace, minus the producers this run excludes. That is the
behaviour the Codefly guide documents: a producer outside the run is dropped
at WARN by the resolution (judgeDroppedValue, core's run-wide drop), and the
warning now names the producer beside the consumer, the value and the
reference position. Only membership is relaxed:
- a producer the workspace does not have is still refused as "not a service
of this workspace";
- the endpoint must still exist and be visible to the consumer's module;
- a producer the run excludes (--exclude-dependency, a run profile) still
refuses the run naming the group, the producer and both ways out, as the
guide's coupling rule says ("a reference is never silently dropped").
Renders and `codefly doctor` are unchanged: their graph is the whole
workspace, so the added fallback never answers for them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
codefly-dev/core 4ab16f55: an interface dependency no unit in the workspace implements stays unbound instead of refusing every load (a module's own workspace, so `codefly generate contracts <module> --check` and WithDependencies work again), an unbound non-external one is refused by the dependency graph and the closure, and the host hands each agent its composition's interface binding in the load identity, so an agent no longer loads a workspace that imports another by version to bind it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tables With no --output, a service that published a contract but had no go.mod at its Go source root failed by name (8df6849). A module with a Go service and Python services that publish contracts, compute's shape, could not generate its permissions at all without --output. A Python service (a python-* agent) is now skipped with a note: nothing in it reads a permissions table, because the authority's hop in front of it decides every call, so a generated Python table would have no reader. The Go services get their tables as before, and --check covers exactly those. Whether a service is Python comes from its agent, not from the absence of a go.mod, so a Go service missing its go.mod still fails by name. Tests: the fixture module carries a Python service publishing a contract; the default run generates only the Go services' tables, keeps the Python service's methods out of them, and names it as skipped; --output still holds the whole module, the Python service's methods included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d and logged Core's module contract now lets a binding declare `optional: true` (core a3bf508a): a composition that fills none of its slots resolves without it, one that fills them resolves it as a mandatory binding, and one that fills them in part is refused by name. The render takes core at that commit and honours the resolution: the delivered manifest's authority carries only the bindings core resolved, and each binding core omitted is logged with its reason, naming every unfilled slot, so a composition without ratings renders robin rather than inventing values for a binding nothing would use, and the omission is never silent. Tests: unfilled (absent, and supplied empty as a module's declared configuration supplies a key nobody set) omitted from the authority and the delivered manifest, logged, with the audience not composed and its calls never read; filled, delivered; filled in part, refused by name; mandatory unfilled, refused as before. docs/module-manifests.md states the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…implements A host service implementing codefly.dev/composition on one endpoint (saas accounts on connect) was sent every manifest through ApplyManifest even when host.delivery names its signed REST route, and ApplyManifest there is internal: "the host refused the module manifest of tracing through the composition interface: PermissionDenied: internal service credential required". HostImplements now judges the delivery endpoint, for the delivery path and the policy preflight alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a run, the host gateway's own startup carriers already skip a route that cannot be resolved and report it. A module's delivery did not: one solution consuming an endpoint the module no longer declares (the wiki's documents/documents/rest, gone with the module's REST face) failed the whole manifest, so the host never learned the module. A run now warns and delivers without that route; a render still refuses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A local run reported every host route with no mapping as "this run has not allocated <producer>'s address yet", including a route to an endpoint the producer no longer declares, which no allocation will ever answer. The run now reads the producer's declared endpoints first: a declared one still waits on its address; an undeclared one is refused as `<producer> declares no endpoint named "<endpoint>"`, as a render already says, and a producer whose endpoints cannot be read says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes at the catalog publication and configuration owners. The generated listener inventory may span protobuf packages, including all 14 host procedures. Validate it through Core rather than filtering by one owned package; reject missing catalogs for multi-package policy delivery. Pins Core 4e7f8ef01ed2 for additive workspace configuration extensions, named key conflicts, explicit env-list append and YAML map/list assembly. Reuses the existing profile reader, key matching, origin tracking and listener inventory; no second runtime configuration resolver or domain-specific role merger. Docs: docs/commands.md and regenerated docs/cli-reference.md in this commit. Regressions fail before the fixes. GOWORK=off tests pass for cmd/generate, pkg/generators, cmd/config, cmd/environment, pkg/orchestration and pkg/gitops; go vet ./..., reference check and isolated CLI build pass. Core configurations, composition and resources tests and go vet ./... pass. No live deployment or SaaS contract regeneration was exercised.
Fix at the native delivery integration owner. Reuses the deployed frontend builder and the owner package.json build script, BuildKit content caching, canonical archives and the artifact cache. No second builder, hand-written frontend command or parallel cache receipt is introduced. Native builds snapshot current Git-selected source, including edits and sibling packages, without old frontend output or node_modules. Build failures and missing manifests refuse by component name with build output; stale dist is never a fallback. Docs: docs/commands.md and docs/module-manifests.md. The regression reproduces the original missing-mf-manifest refusal before repair. GOWORK=off tests pass for cmd/run, pkg/solutionrun, pkg/gitops and pkg/composition; go vet ./... and an isolated CLI build pass. Real Docker qualification proves unchanged inputs reuse a random build marker and a source edit invalidates it; the deployed builder qualification also passes. The final local-source adjustment was rechecked with both local suites, vet and the real cache test. No full platform-obin run, live host admission or browser rendering was exercised. PR #1135 overlaps only in deployed builder selection; this change reuses the existing frontend build seam.
Fix at the native launcher and projection owners. Poll every five seconds and renew each projection when at most 20% of its lifetime remains, using wall time without a monotonic component so laptop sleep counts. Track successful issuance conservatively; failed writes remain due, report the workload and underlying reason in the run log, and do not prevent other renewals. Reuses Core's native issuer, lifetime, atomic credential writer and Wool logging. No JWT parser, second issuer or token cache is introduced. Documentation: docs/module-manifests.md. Validation: affected identity and run suites, including race checks; wall-clock jump, threshold, partial failure, logged reason, next-poll retry and cancellation regressions. Restoring the old timer fails the retry regression. Repository-wide go vet, generated reference check and isolated CLI build pass. No real laptop suspend or live Robin scoring flow was exercised. Branch lint follow-up uses os.Root for frontend staging writes as well as reads, avoids catalog value copies, and removes shadowed errors. Full golangci-lint reports zero issues. The affected GitOps, solutionrun and orchestration suites also cover these changes.
The Go and native gates failed before compilation because the reader-app ID and key are not configured. Reuse the existing organization GH_PAT already used for companion checkout and publication, passing it only to the existing repository-scoped module downloader/bootstrap. Missing credentials still refuse; no test, requirement or permission check is skipped. Apply the same authentication fix to companion and release downloads. Pin the coordinator's Core PR #835 revision 88f4434c68c749c03b88b80e83150cff69643b74. Docs: docs/development.md and the install action input contract. Actionlint and shell syntax checks pass; Go validation and hosted CI will verify the new pin and configured credential.
GitHub's merge build failed because main's new rotation path still called generatorFormat after this PR moved encoding/default resolution to EnvironmentSecretGenerator.Encoding. Use the shared owner for rotation too. Preserve current main's already-landed changes without rewriting this PR's history. Docs: docs/deployment-secret-safety.md. Deployment-secret, environment, deploy-command and environment-command suites pass on the combined tree, as do go vet ./... and generated CLI reference verification. No gate is skipped or allowlisted.
…end built per input change, host-decided withdrawals Native delivery (cmd/run, pkg/gitops): - The run keeps one connection to the host's codefly.dev/composition endpoint, resolved by the flow as readiness resolves it (native instance, TLS when the endpoint declares itself secured), reused by every delivery, retry and withdrawal, redialled only when the host answers elsewhere, and closed when the run stops. host.delivery is read with DeliveryEndpoint(). - The legacy HTTP delivery path is deleted: the POST route, the HTTP applied-state observer and its credential, the envelope writer and the fixture administrator it signed in as. A host whose delivery endpoint does not declare codefly.dev/composition receives nothing, by name. - Refusals keep their gRPC code. Only Unavailable, DeadlineExceeded and ResourceExhausted are retried; Unimplemented, Internal, InvalidArgument, PermissionDenied, FailedPrecondition and unclassified codes fail at once. - The local generation record no longer tombstones a withdrawal: a re-added module is the next generation and the host decides. A host that still holds the withdrawal refuses with ErrWithdrawn and the message says to restart the run (the local authority keeps applied deliveries in memory); a withdrawal refused because the host already holds one is complete. - HostImplements fails closed: a host module that does not load is an error with its reason. A host the workspace does not compose is held to receive every policy, so the every-method-needs-a-policy rule is never dropped. - Publication refusal returns validateSolutionUnits' reason. - Deleted the unused BeforeRuntimeStart, NativeWorkload.Declared and DeferFrontend, NativeDeliverer.Post, CompositionDelivery.Post and the applied observer identity. Frontend (pkg/solutionrun): a declared page is built only when its own inputs change (tracked files of its npm project, lockfile included, with working-tree content), outside the delivery lock, with the frontend's own npm build natively and Docker only when the owning service runs in a container. Untracked files never reach a build; a symlink in the project is refused by name. CI: authority is public, so GOPRIVATE, the GH_PAT handoff and the unused read-authority action are removed. `ci deploy` checks the module set `deploy service` checks, with an unchanged deploy graph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he frontend build reads Re-review r19 of #1158. B1: the saas-hosted compositions on main (platform-obin, platform-core, core-solutions lastlogin-go: host.delivery saas/accounts/rest) still deliver over the legacy HTTP route, since saas on main implements no codefly.dev/composition. That path is restored with unchanged behaviour: the manifest route, the read-only applied observer and its identity, the envelope writer and the fixture administrator, and the HTTP status classification. The transport is chosen from the host's declaration only: a host module that does not load is an error (no fallback), and the run logs why when it takes the HTTP route. Its deletion follows saas#1217 and the compositions' re-pointing. M1/m1: the native frontend build digests exactly what it reads and reads a staged copy of exactly that. The set is the npm project's files, tracked or new but not ignored, never an untracked .env; its local file:/link: packages; and the top-level regular files above it, without the services' code or Go sources for a component-root project. A new untracked file or a sibling-package edit rebuilds, and a Go edit does not. npm ci runs in the staged copy, never in the component's node_modules. Docker builds the same copy. m2: the held-withdrawal refusal is still recognized by core's ErrWithdrawn text, now labelled as a stopgap. codefly.dev/composition has no typed refusal detail (codefly-dev/authority#2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…withdrawal; bound staged frontends Re-review r23 of #1158. F1: a `file:`/`link:` dependency that names a file (a vendored .tgz, as every solution depending on the wiki's vendored UI has) is now a frontend source at the path npm resolves. It is digested and staged, so `npm ci` with a cold cache installs it from the staged copy. The install key covers the lockfile and those tarballs, so a tarball replaced in place reinstalls and rebuilds. A local dependency naming nothing, or something outside the repository, is refused by name. F2: over the legacy HTTP route, saas's 409 carrying core's ErrWithdrawn wording maps to Reason=ErrWithdrawn, as the composition path does. A second withdrawal after a refused re-add therefore completes rather than staying pending. Both paths share withdrawnRefusal, labelled as a stopgap (codefly-dev/authority#2). F3: a module's staged copy (with its node_modules) is removed when the module leaves the composition, and every copy when the run stops. The bundle records stay. Past 2 GiB per environment, the least recently built copies of other modules are removed after a build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…after the #1128 squash Brings in ba15066 (native host federation), efb61fd (pre-start admission of selected workloads) and eee37c1 (hop routes through composition configuration; authority at cac1f6b). 625e66f's tree is the squash 672416b, so only those three commits' changes are merged. Conflicts are resolved keeping both sets of fixes: - One composition connection per run (compositionHost) instead of the per-attempt CloseTransport. - A ready delivery builds its frontend before the delivery lock. Admission builds nothing and keeps the payload already published. - nativeHostServices uses the fail-closed HostImplements. - NativeWorkload.Declared/DeferFrontend and the runtime-start seam are kept, now that admission consumes them. - The admission tests name the host's delivery endpoint. - All three authority libraries are pinned at cac1f6b. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Runtime #311 admission restoration found a dependency API defect at CLI eee37c1, still present in #1158 head 1371a49: I am preparing a small patch in the existing delivery checkout that reuses |
…zen snapshot before delivery Re-review r31 of #1158. M1 (security): a hop listener's ROUTES methods were the API catalog's route and procedure lists, which no digest binds, while the authority's policy table was read from the contract bytes. A catalog listing only GET /v1/things/{id} beside a document that also declares the stricter GET /v1/things/export made the hop check export calls against the {id} policy. Both are now read by one reader: pkg/methodpolicy, extracted from gitops so orchestration can use it without a cycle. ServiceMethodPolicies and the hop routes both call methodpolicy.Contract on the same digest-checked bytes, so ROUTES is the delivered table, listener by listener. The probe is now a test, TestHopRoutesAreTheDeliveredPolicyTable; it fails on the previous head. m1: a Swagger 2.0 basePath now spells ROUTES and policies alike as "<VERB> <basePath><path>". m2: startup admission and readiness delivery re-check the hop's frozen routing snapshot under the delivery lock before reading any policy table, and send nothing when it moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ority#1) All three authority libraries (authority-client, delivery, hop) move from the branch commit cac1f6b to the merge commit c9cef7470d20 on authority main, through which cac1f6b stays reachable. Nothing depends on a branch SHA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
For the coordinator: pushed Runtime #311 now selects that immutable revision locally, but the real admission rerun remains pending: disk exhaustion interrupted export; after disk recovery Docker answers reads yet Contract generation hangs in Docker ContainerCreate (captured stack), leaving an incomplete export that I will not commit. No Documents/provider calls. Explicit broader gaps from review, owned by Core/CLI: the SDK process runtime context can disagree with saved per-service runtime preferences when resolving workspace endpoint references; repeated SetEnvironment leaves removed keys until ReleaseEnvironment. The patch docs state these limits. These are not fixed or qualified by this one-shot admission witness. Please fold the delivery fix into #1158; I have not changed your branch. |
|
Runtime follow-up: the CLI configuration fix is pushed as 68740f8 and resolves anonymously from proxy.golang.org. Full CLI configuration package tests pass. Docker still refuses progress: a bounded |
…the host Review r53 of 193494a: withholding a binding to a module outside the run dropped what the local authority supports on purpose, an audience the run does not supervise (the default fixture's model stand-in, a playground's bridge to its model provider). The authority decides a binding at call time, so before #1158 such a binding was delivered and served. A binding is now delivered as declared whatever it targets. A binding to a module nothing composes grants nothing, and the authority refuses a call through it by name ("audience X is not composed"). Admission keeps #1158's refusal for exactly one target, the host's own module, which the run declares to no authority; a module selected only through services with no identity workload (a datastore) is no longer refused. The selection is read once per admission for both decisions, and a binding outside the run is reported once per module and binding, not on every start. Tests: a binding to a module outside the run, to an identity-less selected module and to a selected module is delivered unchanged, every selected module with an identity workload is admitted with its own workloads only, and the outside-the-run warning is logged exactly once across two admissions; the host binding is still refused before anything is sent. Sabotage-checked: #1158's broader refusal fails the outside and datastore cases, and dropping the once-guard fails the warning count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What we are trying to do
Fix the findings of the post-merge review of #1128 (report r6: 1 blocker, 5 major, 5 minor), and of its re-review (r19: the legacy HTTP delivery restored for saas-hosted compositions, the frontend digest matched to what the build reads, staged native builds, the withdrawal text match labelled as a stopgap). This PR is cut from main at 672416b. It is not to be merged until it has had its own full review.
Per finding
dc41de4126ae, which is only on unmergedauthority/visionc9cef747. All three authority libraries (authority-client, delivery, hop) are pinned to that commit on authority main,v0.0.0-20261025214748-c9cef7470d20, so nothing depends on a branch SHA.solutionrun.LocalFrontenddigests exactly what the build reads, and the build reads a staged copy of exactly that. The set is: the npm project's files, tracked or new but not ignored; the local packages it depends on (file:/link:, transitively); the lockfile; and the top-level regular files of each directory above the project. An untracked.env/.env.*is never included, matching deploy, which builds the committed tree. A project at the component root leaves out the services' code and every Go source. A Go edit builds nothing. A new untracked file or a sibling-package edit rebuilds. The build runs before the delivery lock, using the frontend's ownnpm ci(only on a lockfile change) andnpm run buildnatively, in the staged copy under.codefly/run/<env>/frontend/<module>/, so the component's ownnode_modulesis never touched. Docker is used only when the owning service runs in a container, and builds the same copy. A symlink among the sources is refused by name. A failure carries its command and output.settlenever refuses: a delivery after a withdrawal is the next generation, and the host decides. A host that still holds the withdrawal answersErrWithdrawn, and the refusal says what to do: stop the run and start it again (the local authority holds applied deliveries in memory). A withdrawal refused because the host already holds one counts as complete. Stopgap:codefly.dev/compositioncarries no typed refusal detail, so the withdrawal is recognized by core'sErrWithdrawntext in theFailedPreconditionmessage. The interface gap is filed as codefly-dev/authority#2.launcher.host(compositionHost) holds one connection per run. It is reused across every delivery, retry, admission and withdrawal, redialled only when the host answers at another address, and closed inlauncher.close(). Codex's per-attemptCloseTransportis dropped in favour of it.validatePublicationPermissionsreturnsvalidateSolutionUnits' own reason.HostImplementsfails open(bool, error). A host module that does not load is an error with its reason, and the run then picks no transport. A host the workspace does not compose (a module deploying to a cell from its own repo) is held to receive every policy, so the requirement is never dropped. The legacy HTTP delivery is kept, unchanged in behaviour, but taken only when the host's delivery endpoint declares nocodefly.dev/composition, and the run logs that reason on every delivery that takes it.HostStatusErrorkeeps the gRPC code. OnlyUnavailable,DeadlineExceededandResourceExhaustedare retried. The legacy HTTP delivery keeps its status rules unchanged.Flow.GRPCEndpointTargetresolves the endpoint as readiness does: the native instance's dial address, plus TLS when the endpoint declaressecured. Readiness now dials through the sameGRPCTarget.Dial. The launcher usesHost.DeliveryEndpoint().PostNativeDeliverer.PostandCompositionDelivery.Postare deleted.World.BeforeRuntimeStart(nowBeginRuntimeStart/BeforeRuntimeStop) andNativeWorkload.Declared/DeferFrontendwere deleted in round 1. Codex's merged pre-start admission (efb61fd) is now their consumer, so they stay..githubis back to its pre-#1128 state. Theread-authorityaction anddownload-go-modules.share deleted, and so are the README and docs paragraphs about a private authority.ci deployanddeploy servicecheck different module setsci deployrunsCheckCompositionPermissions(..., module.Name)exactly asdeploy servicedoes, and its deploy graph is the same as before #1128.Authority pin: re-pinned to authority main's merge commit
c9cef747(authority#1), with no branch SHA left.Round 3 (r23)
file:/link:dependency that names a file (a vendored.tgz, as lastlogin-go, summary, excel-extract, robin-nest and lastlogin-python have on the wiki's vendored UI) is a frontend source at the path npm resolves. It is digested and staged, sonpm ciwith a cold cache installs it from the staged copy. The install key covers the lockfile and those tarballs, so a tarball replaced in place reinstalls and rebuilds. A local dependency that names nothing, or something outside the repository, is refused by name.ErrWithdrawnwording is recognized the same way as over gRPC. A second withdrawal after a refused re-add therefore completes instead of staying pending. This is the same labelled stopgap, composition: a typed refusal detail for ApplyManifest (withdrawn binding, stale generation) authority#2.node_modules, is removed when the module leaves the composition, and every copy when the run stops. The bundle records stay. Past 2 GiB per environment, the least recently built copies of other modules are removed after a build.Round 4 (r31)
pkg/methodpolicy(extracted from gitops, whichServiceMethodPoliciesalso uses), over the digest-checked contract bytes. The catalog's own route list no longer names a route. Before this, a catalog could list onlyGET /v1/things/{id}while the document also declared a stricterGET /v1/things/export, and the hop then checked export calls against the{id}policy.TestHopRoutesAreTheDeliveredPolicyTableturns that probe into a test: it failed on the previous head and passes now.basePath. ROUTES and the policy table both spell<VERB> <basePath><path>, so a basePath module is routable and checked against the policy it delivered. The test covers this.TestAdmissionAndDeliveryRefuseAMovedRoutingSnapshot).Merged: Codex's follow-ups on
delivery/through-the-composition-interfaceMerged with a merge commit, not a rebase. Only the three commits after the #1128 squash were merged (
625e66f1's tree is the squash672416bb's):ba150660fix: resolve native host federation before requiring authored configurationefb61fd1fix: admit selected workloads before native service startupeee37c11fix: derive hop service routes through composition configuration (hop-routes/ROUTESthrough the core configuration loader, watcher admission refusing a changed route inventory, partial mapping publication fixed; authority pinned atcac1f6b)How the conflicts were resolved, keeping both sets of fixes:
CloseTransport.nativeHostServicesuses the fail-closedHostImplements.go.modfirst pinned all three authority libraries atcac1f6b. Round 4 re-pinned them to authority main's merge commitc9cef747, through whichcac1f6bstays reachable.The legacy HTTP delivery stays, for now
The saas-hosted compositions on main still use it. platform-obin (
workspace.codefly.yaml), platform-core and core-solutionssolutions/lastlogin-godeclarehost.delivery: saas/accounts/rest, and saas on main implements nocodefly.dev/composition(only the unmerged module-saas-starter#1217 does). This PR therefore keeps the HTTP route, the HTTP applied observer, the envelope writer and the fixture administrator, with their behaviour unchanged. What did change: the transport is chosen only from the host's declaration, an unreadable host is an error and never a fallback, and the reason is logged when HTTP is used. Deleting the path is a later PR, after saas#1217 merges and those three compositions pointhost.deliveryat an endpoint that implements the interface. "Legacy means delete" applies once nothing calls it.What was reused, and what was deliberately not built
reachableNetworkInstance,networkInstanceDialAddress) and the endpoint'ssecureddeclaration, for the composition dialcomposition.RetainCanonicalArchiveand the artifact cache, for bundlesbuildFrontendBundleand the pinned builder image, for the container pathsolutionhost.ErrWithdrawn), for terminalityChecks
go vet ./...go testforpkg/gitops,cmd/run,pkg/orchestration,pkg/solutionrun,pkg/run/fixture,pkg/run/identity,cmd/ciandcmd/deploygo test -racefor./cmd/run,./pkg/orchestrationand./pkg/gitopsNot verified
npm/dockerexecutables.CODEFLY_FRONTEND_BUILD_QUALIFY=1) was not run.🤖 Generated with Claude Code