Repository navigation
feat(accounts): serve the published host interfaces as adapters — rung 1, with the gap list - #1217
Conversation
…policy sidecar; the gap list accounts declares it implements the four published host interfaces (authority, composition, installation, session at 0.1.0), carries their protos under proto/ beside its own (bindings via codefly generate proto), and serves them as adapters over what it already does: Keys and Check over the Work Context authority; Issue by owner shape over StartTask, StartInstallationTask, StartChildSession and ExchangeAudience; List over ListInstallations; SignIn over fixture Authenticate. Where no path exists the adapter refuses by name: workload-owned Issue (gateway workload endpoint), ApplyManifest (signed delivery), Install and GrantToTeam (target and role resolvers). The interface protos carry no saas.policy option on purpose; the host's policy for them is business.InterfaceMethodPolicies, read by RPCPolicies when a descriptor carries none, with the catalog descriptions beside it. The solution-side InstallableService is not served. Known red, by design of this branch (HOST_INTERFACE.md): the service catalog assumes one proto package and Connect bindings are keyed by short service name, so cmd/service-catalog refuses the four packages and the registrations cannot be regenerated; the drift tests fail until both are generalised. go build and the policy tests pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Naming gate, tree check on this branch: 733 forbidden names, all of them the interface packages' publisher name as it appears in the copied protos and in every generated file ( This is not something to allowlist. It is a design decision for the owner, recorded in the authority repository's README: the host's own contract cannot be published under a consumer's name if the host repository is to carry it. Either the interface publisher and package move to a neutral namespace owned by the host (so the authority repository publishes |
The published interfaces now carry the host's names (publisher codefly.dev, packages host.*.v1 under proto/host/), so this repository carries no consumer name in the copied protos or in anything generated from them. Policy sidecar, adapters, bindings and the design note follow the rename. go build and the policy tests pass; the catalog gaps of HOST_INTERFACE.md are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…laration Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ed names; two comments Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Namespace settled: the interfaces now carry the host's names (publisher |
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s; registrations resolve by full name The service catalog has one api_package, the service's own, and now lists the interface_packages it implements beside it (catalog.proto field 9). The compiler accepts a procedure of either; the validator likewise; the frontend catalog, the gateway route aliases and the mesh gates read the API package only, since an implemented interface is not a frontend plugin's client and has no compatibility alias. cmd/connect-registration resolves bindings by full service name first (two services may share a short name across packages) and imports each implemented package's generated Go and Connect packages under derived aliases. The authorization matrix counts services by full name. The adapters now enforce the floors their sidecar policies declare (org member for a person-owned Issue and for List; org admin for Install and GrantToTeam), which the lockstep test requires. The team grant names its organisation, as the interface now does. The naming gate skips generated/service-catalog.json, which crossed its 512 KiB scan cap at 40 services: every string in it is a scanned proto identifier or a scanned Go description. Same reasoning as its descriptor skip; a tool fix, named for review. Catalog, registrations, authz catalog, routes, matrix, topology and frontend catalog regenerated. go build, the business, adapters and cataloggen tests and the naming gate pass. HOST_INTERFACE.md: gaps 1, 2 and 9 closed; 3 to 6 and 8 stand. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Gaps 1 and 2 are closed on the branch: the catalog carries the implemented interface packages beside its API package, registrations resolve by full service name, and the whole generate sweep runs. Business, adapters and cataloggen tests pass; the naming gate is clean (it now skips the generated catalog, which had crossed its size cap at 40 services — a tool fix named in |
…th emits host_binding_applied itself Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…follow the published interface Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… this package already has Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…against accounts; the digest check on the copied protos The harness boots the test store, seeds the dev-admin fixture, wires the sign-in path as the business tests do, configures the Work Context authority against the store, serves the Connect listener on an ephemeral port and runs the published interface suite (github.com/codefly-dev/authority/libraries/conformance/go, descriptor-driven, linking no generated package) over gRPC. Opt-in through HOST_INTERFACE_CONFORMANCE=1 because it boots a store. Green on the first run: Keys; sign-in; an unknown fixture token refused by name. Red, recorded as the gap list in HOST_INTERFACE.md: a module must be installed per organisation before it is an audience; the host has no effective-permissions read for an empty ask; an unknown audience is refused PermissionDenied where the contract says NotFound; workload Issue, ApplyManifest, Install, GrantToTeam as before. Adapters: a fresh task id per person-owned Issue; the session returns the access token as the bearer; List is an administrator's read over ListOrganizationInstallations; an unknown fixture person is NotFound. The copied interface protos carry DIGESTS.sha256 and a test that fails by file name when either side drifts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The rung 1 harness is on the branch ( |
… composed modules are installed everywhere; a hidden refusal names its reason Composition installs a module at every organisation (contract decision of 2026-10-22): business.ComposedModuleInstallations answers the live module authority and the seal store for a composed module's installation, held by every member, and wraps the Postgres store for everything else. A control-plane membership read, WorkContextOrgMember, sits beside the other seal reads. The harness wires it; production keeps its reads until the registry derives these rows itself. An uncomposed audience is refused NotFound by name before any mint, through ModulePrincipalID against the derived registry. Check is authenticated by the token it checks and is public like Keys. mapWorkContextError takes the context and logs the one case the caller learns nothing, "cannot issue Work Context", which until now had its reason recorded nowhere. Suite: 4 of 10 green (Keys, sign-in, the whole person line, every refusal with a reason). The six red lines are each a host change of their own and are the gap list in HOST_INTERFACE.md, with one new gap: the host authenticates a child hop by the owner's bearer where the contract authenticates the holder of the parent token. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Suite against accounts: 4 of 10 green now, including the whole person line (issued, checked, refused elsewhere). What it took, each at the owning place: composed modules are installed at every organisation ( |
|
Correction to my previous comment: the regular test run had three failures that my pipe hid behind a green exit code. All three were counted tripwires moved by Check becoming public (impersonation-restricted set 62 to 61 in two tests, public routes 56 to 57); the baselines are updated with the reason in their comments and the three tests pass. business and infra passed as stated. |
Check is authenticated by the token it checks, so it leaves the impersonation-restricted set (62 to 61) and joins the public routes (56 to 57). Each baseline says why in its comment. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…edential alone; an operation context names its binding ModulePrefixOfWorkload names the one composed module a workload credential authenticates, through the execution reviewer and the existing reverse lookup from a reviewed workload to the principal declaring it; no caller types a module name. The interface's Issue, for a workload, is the host's operation context to an installed audience when a binding is named, and the module's own context to this surface otherwise; an undeclared binding is NotFound by name. The interface's Issue is authenticated by the request's shape (a person by the bearer, a workload by its credential, a child hop by the parent token and the actor's credential), so both interceptors let a bearer-less call of that one method reach the adapter, which refuses a bearer-less person by name; a bearer, when present, is verified as on every other method. An operation context now names the binding it exercises: the seal source resolves module-operation/<principal>/<binding> from the installed grant at the revision of its terms, and the operation-task mint seals it. Until now a module operation context carried no binding, which is what left every runtime hop "stale" at the revision check. A composed installation's scope is the declared grant itself, since the manifest is the ceiling. Suite: 5 of 10 green (Keys, sign-in, the person line, every refusal with a reason, the workload line). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… and the actor's credential childHop verifies the parent as issued by this host, names the acting module from its credential, re-reads the owner's authority as the owner (the parent token is what proves the owner), refuses a widening hop as PermissionDenied and derives the hop with core; the host's own child session is authorized by the owner's bearer, which the module acting does not hold. The hop names no binding: core keeps the owner's seal installation on a hop and requires a named binding to be granted within it, and a module's binding is granted within its own installation, an open decision recorded on the interface repository. The bearer-less pass-through on the interface's Issue applies only to a call carrying no credential at all; a forwarded identity is admitted as before. The operation-task mint names a binding only when the authority has one. The harness composes documents, org-directory (the page module, one workload, one binding to documents) and model. Suite: 6 of 10 green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Suite against accounts: 6 of 10 green (Keys, sign-in, the person line, every refusal with a reason, the workload line, the child hop). New since the last comment, each at the owning place: a workload is named from its credential alone through the execution reviewer; an operation context now names its binding in the token (the seal source resolves |
|
Handoff written: obin-ai/handbook branch |
Both sides added a resolver for a module's binding id to the seal source: the installed-binding ids (installed-content/, installed-actor/) and the module-operation/ id. Their prefixes are disjoint, so the seal source tries the installed ids first, then the module-operation id, then a source delegation. The installed read path now passes its context to the error mapper, which logs the refusal reason. AUTHZ_MATRIX.md is regenerated.
ApplyManifestRequest carries the delivery as signed: the manifest document, the DSSE envelope over it, and the delivering workload's credential. Regenerated with codefly generate proto over the template's saas and host inputs; DIGESTS.sha256 updated. The adapter behind it is the next change.
StartTask takes operation_bindings: binding ids of the audience module. The host resolves each one the way the module's delegated exchange will (ModuleOperationAudience: the binding as installed at the organization, narrowed by that installation), adds its invoke scopes beside authority_scopes, keeps each resource only if the person holds it, and refuses by name a binding the person holds nothing under. So a consumer names the binding and never its resource ids, which come from the installation. Direct human-owned mints only.
…to installed organisations Every issue from one delegation carried the delegation's id as its hop id, so the journal kept only the first issue's binding and scope set and held every later one to it (r136 B1). A delegated hop is now recorded under the delegation within the task its issue opened, and the readers ask for that row and the hop's own id. The unit-test journal keeps the first row on a conflict, as Postgres does. Journal and chain-read refusals log the store error (r136 m3). The read oracles answer only about organisations the caller is installed in: CheckAuthorizationRevision and AuthorizeEvidenceRead refuse, before reading, an organisation where the calling module has no installation, alike for one that does not exist (r136 M3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…an installation A module refused as itself on the host interface is Unauthenticated with the reason; a forged token stays PermissionDenied as the shared suite requires (r136 m4). Delegate refuses a module not installed in the grantor's organisation, by name (r136 m5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gated issues on real Postgres The conformance test no longer waits for a switch CI never set and absorbs the fresh-store test (r136 M2); the library is pinned at authority main d091cc7a (core 8af4b787 with it). The harness reads the Connect server's error and retries a port lost between probe and bind (r136 m1). New cases: two issues from one delegation in both orders (r136 B1), and Delegate to an uninstalled module (r136 m5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A renumbered ledger left a database at a version this source also has under another name, and Up skipped a migration and failed dirty later (r136 m2). The runner stamps every applied migration's name on its version table and refuses a database whose names the ledger contradicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve actionable authority refusals, delivery digest evidence, migration recreation guidance, and held ephemeral conformance listeners. Regenerate narrowed client libraries with released CLI v0.1.215 and restore descriptor closure qualification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain the Codefly-generated proto3 JSON field shape and operation metadata required by the pinned toolchain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Require target generation and installation revision on explicit approval, retain an idempotent consent receipt, and refuse replay after delivery or narrowing. Regenerate contracts and clients with Codefly v0.1.215. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ance Pins the authority conformance library to conformance/consent-on-0.2 (codefly-dev/authority 6870db87), whose installation lifecycle case binds Install to a reviewed ConsentApproval. The fixture's Review hook reads the organization's catalogue offer (target id, applied generation) and the installation listing's revision through the generated Connect clients. DIGESTS.sha256 records the published installation.proto that now carries ConsentApproval. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…proto digest codefly generate contracts saas-starter (CLI 0.1.215) copies DIGESTS.sha256 into the exported accounts contracts; the previous commit recorded the published installation.proto digest, so the copies follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page sends the reviewed target, generation and a fresh operation id with every install (7a4342a); the container tests still expected the request without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reuse source-operation verification and installed module actor bindings for saas-directory. Keep organization membership and tenant scope checks in the existing handlers, preserve bearer admission, and name verification refusals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain revoked review state, advance revision for every fresh receipt, and supersede pending installation work under the existing lifecycle fence. Validate descendant scopes against the original journaled grant, follow all host listing pages, and submit the listed revision from the install page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3e9e56b to
61100e0
Compare
…rage-through-the-service Brings in #1217 (the published host interfaces as adapters, codefly 0.1.215). Generated files conflicting on this branch's datasource.proto change (both contract descriptors, the catalog, the module package's digests, the SDK's contract, catalog, stamp and datasource_pb.ts) were taken from main and regenerated with codefly 0.1.215 through the chain main documents; a second run is a fixed point. The source-read SDK records the whole authority contract's digest, so it was regenerated once with --force, as its README says; only that digest line moves, and a run without --force is clean. Hand-resolved: - module.package.codefly.yaml: the authored object-storage provides entry main's copy drops is restored. - objectstore client.go, client_test.go, guard_test.go: kept deleted. This branch replaces the host's store client with the composed service; main's mesh-egress admission for that client has no host dial to apply to here. - DATABASE_AUTHORITY.md: main's tenant row plus datasource_object_manifests. - saas-sdk 0.3.39 on main becomes 0.3.40; saas-ui's peer and dev floors, the frontend's exact pin, the README floor and the lockfile follow. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ier 54 #1217 landed migrations 49 to 54, so this branch's unshipped 49 and 50 collide with main's authority_delivery and installation_exposure_revision. They become 55 (datasource_object_manifests) and 56 (last_snapshot_items), content unchanged, with the prose that names them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ct-storage service; the host's store client is deleted (codefly-dev#1235) * feat(accounts): the object-storage connector diffs the current listing against one the host retains An object store answers only "what is there now". objectstore.FilesConnector versions a source by the digest of its listing (key, ETag, size) under its binding, the host retains the listing each version names in datasource_object_manifests (migration 46, tenant-owned, newest four per source), and Changes(from) diffs against it. It reaches the store through a Bucket interface the object-storage service's per-call binding will implement (codefly-dev/service-object-storage#108), and passes connectortest.RunFiles over a scripted bucket. It is not yet what the host syncs through: the provider stays registered non-conformant and runUploadSync keeps running. The upload descriptor now states source-scoped readers in the catalog. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(accounts,saas-ui): a bucket source takes include and exclude key patterns, applied before any fetch UploadDatasourceConfig gains include_patterns and exclude_patterns (bounded RE2 over the key relative to the prefix; exclude wins; empty include admits all) and file_extensions with GitHub's semantics. AddSource and UpdateSource refuse an invalid pattern by field (INVALID_ARGUMENT, a BadRequest violation, and a message leading with the field path), never at sync. The store's listing applies the filter before it counts against max_objects, so an excluded object is never fetched, and the closing listing names only matches: narrowing the filter through UpdateSource tombstones what it now excludes on the next sync. The kit's object-storage connect form, with its shared collection field, moves here from the catalog change and takes the key filter, placing a host refusal under the field it names. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor(accounts,saas-ui): the bucket key filter is the connector's Scope, not the listing client's The owner's review of codefly-dev#1216: the match sat inside objectstore/client.go's List, the file codefly-dev#1211 deletes, so it would have had to move again when the object-storage service replaces the client. objectstore.Scope now holds the host's InScope predicate (uploadKeyScope, as GitHub's SourceConfig.InScope) and the object cap, and Window applies both to whatever listed the prefix: an excluded key is dropped before it is counted, never fetched, and a cut is reported. client.go returns the store's page whole; its cap moved into Scope with the filter. UploadContentClient.List is back to main's shape. The kit moves to 0.12.16 (saas-ui, and ui with it) and the SDK to 0.3.36, past what v0.0.105 released; saas-ui's SDK floor rises to ^0.3.36 because the form sends fields only that SDK carries. package-lock.json is regenerated by npm. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(accounts): object-storage sources read through the composed object-storage service; the host's store client is deleted The host composes service-object-storage and reads every object-storage datasource through its per-call bucket binding (codefly-dev/service-object-storage#108, pinned at 5708d5e): the binding names the source's endpoint, region, bucket and prefix and carries its access key for that one call. objectstore.ServiceBucket pages the listing (bounded) and reads each object at If-Match its listed ETag; it asks Capabilities under the binding first and refuses a service that reports no bound caps, since a release from before the binding would serve its own bucket as the tenant's. runUploadSync takes its snapshot through objectstore.FilesConnector and reads it in batches; the source's key filter and object cap ride on SourceConfig.Scope and are applied before the version is digested. The delivery and the closing listing are unchanged. client.go, its tests and its egress guard are deleted. The provider stays registered non-conformant, now for clause 8 alone: the service's bound read takes one key per call. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * build(ci): the object-storage service is covered by the Codefly vendor image audit Every topology service declares image coverage. The object-storage agent runs its gateway and MinIO images, both pinned by the agent, and builds nothing in this repository, so it is classified as Redis and Vault are. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * build(accounts): the object-storage dependency's generated client, from codefly sync service accounts Declaring accounts' dependency on object-storage/grpc makes the go-grpc agent's sync emit a client file for it under code/external, and the sync-drift phase compares the tree against that output. Generated with CI's pinned codefly; the same command on clean main changes nothing. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(accounts): object storage reads a batch in one call and is registered conformant service-object-storage#111 at 33ba749 serves a batched bound read: GetMany takes up to max_get_many_items keys, each at its listed ETag, and streams them back in request order. ServiceBucket.Read now issues one GetMany per batch, refuses an answer it cannot attribute (out of order, short of the listed size), maps CHANGED and NOT_FOUND to ErrObjectChanged and TOO_LARGE to ErrItemTooLarge, and refuses a service that reports no batch size as it refuses one that ignores the binding. The conformance suite now runs through ServiceBucket over an in-process fake of the service's gRPC API, so clause 8 is measured on the calls the host makes: 120 objects cost one GetMany after the listing. The provider is registered through connector.Budgeted, metered per source credential like GitHub; GetDatasourceCatalog reports accepts_new_sources and AddSource takes a new source with its prefix and key filter. The registration gate finds a connector's suite in the package that implements it, through the meter. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(accounts): a vanished store never closes an object-storage sync, and a missing bucket is never an empty listing The service at 33ba749 can answer a store-level failure inside GetMany as NOT_FOUND for every object, ending the stream successfully. The host's tombstone authority is the closing listing alone, sent only after a successful listing in the same sync and the delivery of every object in it; a NOT_FOUND read is ErrObjectChanged and fails the sync first. These tests pin both halves: a sync whose every read is NOT_FOUND sends no listing, and a List the service answers NOT_FOUND is the store's error, never an empty version. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * build(accounts): service-object-storage at eef1863, codefly-dev#111's head; the client package is unchanged Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(accounts): an object-storage source lists its literal prefix, admission needs a service that can read, and one credential spends one budget From the review of codefly-dev#1235 at a3c4836: - A source's prefix is a literal S3 key prefix, and the service normalizes a binding's prefix as a folder: "/reports/" listed "reports/", and "/" listed the whole bucket, so a populated source could read an empty scope and close with a listing that tombstones every entry. The binding now carries no prefix; every page filters on the literal prefix, which the service passes to the store unchanged, and keys travel whole. A listed key outside the prefix, or a page past the service's own page size, refuses the listing. - Every call to the service has a deadline (30 s; five minutes for a batched read), so a store that answers and then stalls cannot hold a sync and its worker's lease. - Admission needs a composed service that can read a bucket. work.go asks the service, unbound, whether it reads a batch (objectstore.CheckService); with none, or the released 0.0.18 agent, object storage is unavailable: the catalog says so and AddSource refuses with ErrObjectStorageUnavailable. - An upload source's budget is keyed by its credential — a keyed digest of the store's origin and access key id, fixed at connect, as an API source's is — so sources sharing one key share one quota and one provider throttle. A sync a person asked for is interactive and may spend the reserved share. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * style(accounts): the object-storage connector's two unchecked writes, which golangci-lint flagged on this branch A write into a sha256 hash cannot fail, and the test fake's page-token parse falls back to the first page; both discard the error explicitly. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * build(accounts): service-object-storage at a3157779, codefly-dev#111's fixed commit for the codefly-dev#1235 review The service now fails a bound listing the store marks truncated without a continuation token, keeps a binding's prefix literal, bounds every store response to 5 MiB and deadlines each bound operation at 30 seconds. The proto changes in comments only, so the client package is unchanged. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(accounts): the pinned object-storage service is held to the two invariants the host's deletions rest on; pin 358091a0 The closing listing of an object-storage sync authorizes deletions, and two properties of it were the service's alone: that a bound listing's prefix is used literally, and that a listing the store marks incomplete fails instead of answering as complete. Both were wrong in the service once, and a fake of the service cannot catch a regression in it. These tests build cmd/service-object-storage from the module go.mod pins, run it on an in-memory bucket of its own with a scripted S3 endpoint admitted as a composed dependency, and read through ServiceBucket: the store must be asked for "/reports/", "/" and "a//b/" exactly, and an IsTruncated page with no continuation token must fail the listing, empty or not. They fail rather than skip when the gateway cannot be built. Against the pre-fix service (33ba749) the incomplete-listing test fails; against a service patched to strip a leading slash, the prefix test fails. The service pin moves to 358091a0, codefly-dev#111's head after its second review round. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * style(accounts): the contract test's two unchecked closes, which golangci-lint flagged Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(accounts): a composed object-storage service that cannot serve a binding is refused at admission, not at every sync Review r1's major 3 was that the catalog accepted object-storage sources, and AddSource stored them, while the composed runtime (the released 0.0.18, which knows no binding) failed every sync. The refusal now lives in the one place object storage is wired: Service.SetObjectStorage asks the service whether it serves a batched, bound read and refuses to wire one that does not, so the catalog does not offer the provider and AddSource refuses it by name. The test drives that wiring with a service answering as 0.0.18 does and asserts the refusal at admission — catalog, AddSource, nothing stored or audited — and that a service with the binding is admitted. Wiring whatever is composed turns it red. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * build(accounts): object storage runs on service-object-storage v0.0.19, in both pins, and acts on the reason each refusal names The composition selected agent 0.0.18, which has no binding, so on the declared runtime the host refused object storage at admission. v0.0.19 is the release of codefly-dev#111 (per-call binding, batched read, literal prefix, complete or failing listings, bounded responses, per-operation deadlines); the agent version moves to it with `codefly update service`, which rewrote only the version token, and accounts' go.mod requires the same release. v0.0.19 also names every refusal's reason in a google.rpc.ErrorInfo, because one code covers refusals that call for opposite actions. The host read RESOURCE_EXHAUSTED as a throttle — so a listing too long, or a response too large, blocked the credential for every source sharing it and retried the same listing forever — and PERMISSION_DENIED as a refused credential even when the guard refused the endpoint. storeError now acts on the reason: a too-long listing or response is ErrListingIncomplete, an object too large is ErrItemTooLarge, the guard's refusal is ErrDestinationRefused, and only STORE_THROTTLED (or a throttle with no reason) is a rate limit, with the store's RetryInfo. A contract test holds the released gateway to it: a page of 1,001 entries must read as an incomplete listing, never a rate limit; with the code-only mapping it reads as one. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(accounts): a sync never empties an object-storage source that has files, each refusal gets the action its reason names, and one credential is one budget for every source From review r2 of codefly-dev#1235 at 32eb6c8: - g2 (owner decision): a closing snapshot with no files tombstones every entry mirrored from a source, and a service that wrongly answers "empty" — a rewritten scope, a discarded truncation — was proved twice through the real client and the production sync path. The host owns deletion authority, so the rule lives here, whatever any service version answers: a listing that comes back empty for a source whose last closing snapshot named objects is refused (ErrUploadEmptyListingRefused, job code datasource.upload_empty_listing_refused), closes nothing, and leaves the collection as it is. "Has files" is the count the host records when it sends a closing snapshot (datasource_sources.last_snapshot_items, migration 47); a source synced before it is unknown and treated as having files. The retained manifests cannot serve: every listing writes one, so a run of refused syncs would push the last non-empty one out. - g3: typed reasons reached distinct Go errors but one generic retry. Now a throttle is retried no earlier than the store's reset; an object over the service's cap is skipped and the sync goes on, surfacing it without a closing snapshot; a guard refusal or a rejected access key parks the source as degraded with a fixed reason and fails the job terminally; and LISTING_INCOMPLETE is the incomplete-listing disposition. - g1: the budget key was written at connect and only with the host's link key, so existing and keyless sources kept a budget each. It is now derived from the source's endpoint origin and access key id on every use — plain configuration on the same row, no secret — so every source on one key shares one quota and one provider block, with no backfill. - g4: the adapter comment no longer says the service normalizes a binding's prefix. Each behaviour has the test that would have caught it, driven through the real client and the job handler where the review's probes were; each was mutated red. Refs codefly-dev#1211 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(accounts): a refused object-storage source parks through the one park step, and a park that cannot be written retries Round two's refusal parking re-implemented the GitHub public-source park's control-plane write, and dropped its rule: when the degrade write fails, the job retries instead of going terminal, so a source never stops with nothing on it to say why. TestControlPlaneCallSites refused the copy as a new call site. parkDatasourceSource now owns the write and the already-parked check; the GitHub path and the object-storage path both call it, so the call-site list changes by a rename only. A refused upload whose park fails returns the park failure in place of the refusal and retries; TestUploadRefusalWhoseParkFailsRetries holds it, mutated red. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(frontend): kit 0.13.3 and saas-sdk 0.3.37, bumped from main's numbers Main moved @codefly-dev/ui and @codefly-dev/saas-ui to 0.13.2 and released saas-sdk 0.3.36 without this branch's key-pattern fields, so both carry this branch's changes under versions the registry already serves. The kit moves together to 0.13.3; saas-sdk to 0.3.37, which saas-ui's peer floor and the frontend's exact pin now name, since 0.3.37 is the first release carrying an upload source's include and exclude patterns. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(store): renumber the object-storage migrations above main's frontier 48 codefly-dev#1254 landed migration 48, so this branch's unshipped 46 and 47 now sit below the reference frontier and the migration reference gate refuses them. They become 49 (datasource_object_manifests) and 50 (last_snapshot_items), content unchanged, with the prose that names them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(store): renumber the object-storage migrations above main's frontier 54 codefly-dev#1217 landed migrations 49 to 54, so this branch's unshipped 49 and 50 collide with main's authority_delivery and installation_exposure_revision. They become 55 (datasource_object_manifests) and 56 (last_snapshot_items), content unchanged, with the prose that names them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(store): renumber the object-storage migrations above main's frontier 55 Main's hop admission keys took 55, colliding with this branch's 55. The two unshipped migrations become 56 (datasource_object_manifests) and 57 (last_snapshot_items), content unchanged, with the prose that names them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(store): renumber the object-storage migrations above main's frontier 58 Main's authority core took 56 to 58, colliding with this branch's 56 and 57. The two unshipped migrations become 59 (datasource_object_manifests) and 60 (last_snapshot_items), content unchanged, with the prose that names them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Directory Work Context admission
Fix at the Accounts owner: DirectoryService's three Connect reads now accept a person-owned Work Context addressed to
saas-directory. A consuming module obtains it through the existing installedmodule_actoroperation binding and forwards it with the generated DirectoryService client. Admission reuses source-operation signature, audience, expiry, live seal/revision and delegation verification. The shared directory handlers retain requireAuth, current organization membership and verified tenant confinement; bearer admission is unchanged. Directory facts remain SaaS-owned and no RPC or fact is added to the authority interface. No wire contract or generated file changed.Generated-client regressions cover the installed-module exchange and all three reads, foreign-organization refusal even for a member of both organizations, expiry, forgery, wrong audience, current membership and revocation, delegation revocation/unavailability, ambiguous carriers, spoofed identity headers and unchanged bearer admission. This adds no separate authority store or verifier.
Latest local qualification for
b961f6f2: Accountsgo vet ./...passes. The fullgo test -race -json -count=1 -timeout=35m ./...run found only two audience-set assertions needing the new built-in audience; complete adapters and business race reruns pass after those updates. Combined final evidence is 52 tested packages, 6,933 passing tests/subtests, 15 existing conditional skips, and no race reports. All new directory tests execute without skips. Naming, boundaries, whitespace, the new commit identity, prohibited-file and lockfile/token checks pass. This is the current directory-change qualification; the earlier validation notes below record the preceding work's status.Accounts implements the authority interface through its existing identity, Work Context, signed-delivery, installation, scope-grant and audit owners. Signed module and solution policies are persisted with their exact manifest generation and enforced by Check. Datasources remain SaaS-owned.
The consent replay fix is implemented, but this PR still requires a coordinated update in
codefly-dev/authority: publish the new Install approval fields and update the conformance caller, then copy the published proto/digest and advance the conformance pin here. That repository is outside this task's authorized scope. The provenance assertion and conformance suite remain intact and currently fail on this dependency.Consent replay fix
Both human Install RPCs require an approval containing the immutable target ID, reviewed generation, expected installation revision (zero for creation), and a UUID operation ID. The frontend submits the selected catalogue generation; host-owned fixture setup resolves its own explicit approval.
Accounts compares the reviewed generation with applied presence while holding the binding/target locks, and compares the installation revision under the existing installation lock in the consent transaction. A durable receipt binds the original request and actor to the resulting revision. Unchanged retries return the installation without repeating the lifecycle audit; stale generation, stale revision, missing approval and conflicting operation reuse have named refusals. Neither replay after a wider delivery nor replay after narrowing can grant authority. A revoked approval cannot recreate its installation.
Classification: fixes at the existing Accounts installation and contract owners. Reused seams: the target catalogue, organization admin check, installation transaction, lifecycle revision/revocation trigger, tenant RLS, generated clients, and existing generation scripts. No second authority store, compatibility fallback, boundary exception, or weakened test was added.
Earlier review findings retained
:0and reads back its allocated port.Required toolchain and validation
Released Codefly CLI v0.1.215 is required, matching
scripts/ci/install-codefly.sh; proto companion 0.0.18 and Go 1.27.3 were used. The pinned CLI successfully provisions the Accounts dependency graph with its Codefly-ownedegressdependency. Generated outputs are retained in full.Known failures: the pinned external conformance lifecycle caller omits approval, and TestCopiedInterfaceProtosMatchTheirRecordedDigests correctly rejects the unpublished interface change. The authority contract/conformance owner must publish the coordinated update before merge. Historical non-no-reply commits are preserved under the no-rewrite rule; any resulting history-policy failure belongs to the branch owner. No deployment or host release was exercised.