Skip to content

fix(run): address the #1128 review findings - #1158

Merged
antoinetoussaint-byte merged 71 commits into
mainfrom
fix/1128-review
Oct 25, 2026
Merged

antoinetoussaint-byte merged 71 commits into
mainfrom
fix/1128-review

Conversation

@antoinetoussaint-byte

@antoinetoussaint-byte antoinetoussaint-byte commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

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

# Finding What changed Class
1 go.mod pins authority dc41de4126ae, which is only on unmerged authority/vision Fixed. codefly-dev/authority#1 merged with a merge commit, c9cef747. 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. fix
2 Frontend rebuilt in Docker on every delivery, under the delivery lock solutionrun.LocalFrontend digests 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 own npm ci (only on a lockfile change) and npm run build natively, in the staged copy under .codefly/run/<env>/frontend/<module>/, so the component's own node_modules is 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. fix
3 A stored withdrawal permanently blocks a re-added root settle never refuses: a delivery after a withdrawal is the next generation, and the host decides. A host that still holds the withdrawal answers ErrWithdrawn, 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/composition carries no typed refusal detail, so the withdrawal is recognized by core's ErrWithdrawn text in the FailedPrecondition message. The interface gap is filed as codefly-dev/authority#2. fix (recognition is a stopgap)
4 gRPC connection leak 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 in launcher.close(). Codex's per-attempt CloseTransport is dropped in favour of it. fix
5 Publish returned the module-load error validatePublicationPermissions returns validateSolutionUnits' own reason. fix
6 HostImplements fails open It returns (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 no codefly.dev/composition, and the run logs that reason on every delivery that takes it. fix
7 Unmapped codes retried HostStatusError keeps the gRPC code. Only Unavailable, DeadlineExceeded and ResourceExhausted are retried. The legacy HTTP delivery keeps its status rules unchanged. fix
8 Hand-written insecure dial Flow.GRPCEndpointTarget resolves the endpoint as readiness does: the native instance's dial address, plus TLS when the endpoint declares secured. Readiness now dials through the same GRPCTarget.Dial. The launcher uses Host.DeliveryEndpoint(). fix
9 Unused seams and an unsafe Post NativeDeliverer.Post and CompositionDelivery.Post are deleted. World.BeforeRuntimeStart (now BeginRuntimeStart/BeforeRuntimeStop) and NativeWorkload.Declared/DeferFrontend were deleted in round 1. Codex's merged pre-start admission (efb61fd) is now their consumer, so they stay. fix (seams kept once consumed)
10 GOPRIVATE and GH_PAT kept for a now-public authority .github is back to its pre-#1128 state. The read-authority action and download-go-modules.sh are deleted, and so are the README and docs paragraphs about a private authority. fix
11 ci deploy and deploy service check different module sets ci deploy runs CheckCompositionPermissions(..., module.Name) exactly as deploy service does, and its deploy graph is the same as before #1128. fix

Authority pin: re-pinned to authority main's merge commit c9cef747 (authority#1), with no branch SHA left.

Round 3 (r23)

  • Tarball dependencies. A 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, 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 that names nothing, or something outside the repository, is refused by name.
  • Held withdrawal over HTTP. Over the legacy HTTP route, saas's 409 carrying core's ErrWithdrawn wording 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.
  • Staged copies. 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.

Round 4 (r31)

  • One table for hop routes and the authority (security). A hop listener's methods are now the method policy table the authority receives for that listener. Both come from the same reader, the new pkg/methodpolicy (extracted from gitops, which ServiceMethodPolicies also uses), over the digest-checked contract bytes. The catalog's own route list no longer names a route. Before this, a catalog could list only GET /v1/things/{id} while the document also declared a stricter GET /v1/things/export, and the hop then checked export calls against the {id} policy. TestHopRoutesAreTheDeliveredPolicyTable turns that probe into a test: it failed on the previous head and passes now.
  • Swagger 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.
  • Frozen snapshot re-checked. 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 (TestAdmissionAndDeliveryRefuseAMovedRoutingSnapshot).

Merged: Codex's follow-ups on delivery/through-the-composition-interface

Merged with a merge commit, not a rebase. Only the three commits after the #1128 squash were merged (625e66f1's tree is the squash 672416bb's):

  • ba150660 fix: resolve native host federation before requiring authored configuration
  • efb61fd1 fix: admit selected workloads before native service startup
  • eee37c11 fix: derive hop service routes through composition configuration (hop-routes/ROUTES through the core configuration loader, watcher admission refusing a changed route inventory, partial mapping publication fixed; authority pinned at cac1f6b)

How the conflicts were resolved, keeping both sets of fixes:

  • The run keeps one composition connection rather than Codex's per-attempt CloseTransport.
  • A ready delivery builds its frontend before the delivery lock. Admission is "early", so it builds nothing and keeps the previously published payload.
  • nativeHostServices uses the fail-closed HostImplements.
  • The admission tests name the host's delivery endpoint.
  • go.mod first pinned all three authority libraries at cac1f6b. Round 4 re-pinned them to authority main's merge commit c9cef747, through which cac1f6b stays 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-solutions solutions/lastlogin-go declare host.delivery: saas/accounts/rest, and saas on main implements no codefly.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 point host.delivery at an endpoint that implements the interface. "Legacy means delete" applies once nothing calls it.

What was reused, and what was deliberately not built

  • Reused
    • readiness's endpoint resolution (reachableNetworkInstance, networkInstanceDialAddress) and the endpoint's secured declaration, for the composition dial
    • composition.RetainCanonicalArchive and the artifact cache, for bundles
    • buildFrontendBundle and the pinned builder image, for the container path
    • core's admission (solutionhost.ErrWithdrawn), for terminality
  • Not built
    • No local tombstone or reset command: the host is the authority on terminality.
    • No new SDK client: the CLI has no SDK-generated client for a host interface, so this reuses the CLI's own readiness dial rules.

Checks

  • go vet ./...
  • go test for pkg/gitops, cmd/run, pkg/orchestration, pkg/solutionrun, pkg/run/fixture, pkg/run/identity, cmd/ci and cmd/deploy
  • go test -race for ./cmd/run, ./pkg/orchestration and ./pkg/gitops

Not verified

  • No live native run against the local authority, nor against saas over the legacy HTTP delivery. The connection reuse, withdrawal refusal and frontend paths are covered by unit tests with loopback gRPC peers and fake npm/docker executables.
  • The BuildKit cache qualification (CODEFLY_FRONTEND_BUILD_QUALIFY=1) was not run.

🤖 Generated with Claude Code

AntoineToussaint and others added 30 commits October 23, 2026 13:04
…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>
antoinetoussaint-byte and others added 11 commits October 25, 2026 10:27
…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>
antoinetoussaint-byte and others added 4 commits October 25, 2026 16:52
…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>
@antoinetoussaint-byte

Copy link
Copy Markdown
Contributor Author

Runtime #311 admission restoration found a dependency API defect at CLI eee37c1, still present in #1158 head 1371a49: GetDependenciesConfigurations returns service dependency configuration but omits workspace groups. The actual composed Runtime starts; the SDK test consumer receives endpoints/store credentials but no authority/endpoint, hop proof or runtime-execution settings.

I am preparing a small patch in the existing delivery checkout that reuses Flow.WorkspaceConfigurationsFor, with an RPC regression test and a live Runtime witness. I will post the commit for folding into #1158; I am not editing or pushing fix/1128-review. No new PR.

antoinetoussaint-byte and others added 2 commits October 25, 2026 18:04
…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>
@antoinetoussaint-byte

Copy link
Copy Markdown
Contributor Author

For the coordinator: pushed 68740f8168178be1ea1a6b5a433aa8778a17b6ba on the existing delivery branch for folding into #1158. It fixes SDK dependency sessions omitting workspace configuration by reusing Flow.WorkspaceConfigurationsFor and Core serialization into the existing GetConfiguration ProcessVariables carrier. No new mechanism or Runtime workaround. Full go test ./pkg/web/go-grpc -count=1 passes; CLI builds. New regression proves declared secrets arrive while undeclared root credentials stay withheld. Independent review accepted this owner reuse for default native/free one-shot sessions.

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.

@antoinetoussaint-byte

Copy link
Copy Markdown
Contributor Author

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 docker create of the existing proto image, without any bind mount or service start, times out; Codefly export stack is blocked in ContainerCreate. Read APIs/_ping work. I have asked the owner whether to coordinate an OrbStack restart because other agents have active containers. Please coordinate any restart; I have not restarted the shared daemon.

@antoinetoussaint-byte
antoinetoussaint-byte added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit c5a106e Oct 25, 2026
14 of 15 checks passed
antoinetoussaint-byte added a commit that referenced this pull request Oct 26, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants