Skip to content

feat(accounts,saas-ui): a bucket source takes include and exclude key patterns, applied before any fetch - #1216

Closed
antoinetoussaint-byte wants to merge 2 commits into
mainfrom
issue-1211-bucket-key-patterns
Closed

antoinetoussaint-byte wants to merge 2 commits into
mainfrom
issue-1211-bucket-key-patterns

Conversation

@antoinetoussaint-byte

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

Copy link
Copy Markdown
Contributor

Refs #1211, section (d): key patterns on a bucket source. Host-side only; codefly-dev/service-object-storage#108 needs nothing for it. Based on main and independent of #1213 and #1214 (see "Merge order").

What changes

  • Contract. UploadDatasourceConfig gains include_patterns and exclude_patterns. Each is at most 16 RE2 expressions of at most 256 characters, matched against the object key with the prefix removed: unanchored and case-sensitive, with ^/$ and (?i) available. Exclude wins, and an empty include admits everything. It also gains file_extensions with GitHub's semantics, intersected with the patterns. UpdateSourceRequest takes the patterns as fields 7–8 and its existing file_extensions (5), and SourceFetchScope carries the patterns too, so the expected rule covers them.
  • Refused at the call, never at sync. AddSource and UpdateSource compile every pattern. An invalid one, an empty one, more than 16, or one too long is refused as INVALID_ARGUMENT with a google.rpc.BadRequest field violation and a message that leads with the field path: upload.include_patterns[1]: "(" is not a valid RE2 expression: … on add, and include_patterns[1]: … on update. That leading path is stated in the proto comment as the contract, because the TypeScript SDK carries no error-detail types.
  • Applied by the connector, before any fetch. The filter is not in objectstore/client.go, the file Object-storage datasource: the contract says a client offers and flags providers by the catalog, no client reads it, and the connector that exists cannot take a new source (no cursor, no push, no admission) #1211 deletes; it is in the connector package beside it (the owner's review of 2026-10-23). objectstore.Scope (scope.go) holds the host's InScope predicate (uploadKeyScope), as the GitHub connector's SourceConfig.InScope does, together with the object cap. Window applies both to whatever listed the prefix. It drops a refused key before counting it, so an excluded object is never fetched and never uses up max_objects. client.go now returns the store's page whole, and the cap it used to apply moved into Scope with the filter. When the object-storage service replaces the client, the filter and the cap stay where they are. The closing listing is built from what was delivered, so it names only matches.
  • Narrowing deletes. UpdateSource on an upload source replaces the patterns and file types and refuses branch and paths. It runs under the same lock, expected check and source.updated audit event as a GitHub edit (bucket, prefix, include_patterns and exclude_patterns are declared on the event). It then queues the source's pull sync. That sync's closing listing omits what the narrower filter excludes, and the store tombstones it.
  • Access is stated. A source is (bucket, prefix, filter) → one collection → one grant. The catalog description and the connect form now say so.
  • Kit version. v0.0.105 released saas-ui 0.12.15 and saas-sdk 0.3.35, so this PR moves @codefly-dev/saas-ui and @codefly-dev/ui (co-versioned) to 0.12.16 and @codefly-dev/saas-sdk to 0.3.36. These are patches, because the changes are additive. saas-ui's SDK floor rises to ^0.3.36, because the form sends fields that only that SDK carries, and the README floor sentence says why. The portal's exact SDK pin follows, as in feat(saas-ui): a person connects a generic OAuth api source through the kit #1201. package-lock.json was regenerated by npm install --package-lock-only, not edited by hand. feat(saas-ui): the datasources panel offers what the connector catalog admits and names the rest #1214 makes the same ui/saas-ui bump, so whichever lands second has nothing to add.
  • Kit. ConnectObjectStorageForm takes include/exclude patterns, one per line (a comma is part of a pattern), and file types. It places a host refusal under the field it names. objectStorageInputFrom maps its values for the client's new optional addObjectStorageSource. The form and its shared CollectionField move here from feat(saas-ui): the datasources panel offers what the connector catalog admits and names the rest #1214, which keeps only the catalog read, because the form's purpose is now the key filter. Nothing mounts the form yet: the host refuses new object-storage sources until the provider is conformant, and mounting it is part of the change that admits it.

The five tests the issue requires

  1. Matcher: TestUploadKeyScope covers precedence, matching relative to the prefix, case and file-type intersection. TestNormalizeKeyPatternsRefusesByName covers an invalid RE2 (including a backreference) refused with the pattern named, the count and length caps, an empty pattern, and the field in both message and BadRequest.
  2. Validation: TestAddSource_UploadRefusesAnInvalidKeyFilterByField (include, exclude and file type, each by field, nothing stored) and TestUpdateSource_UploadRefusesAnInvalidKeyFilterByField (an invalid exclude and paths on a bucket source, each by field, nothing written, no audit). The add test goes through AddExistingSource, the test-only path into the same addSource validation. The AddSource RPC itself admits before it validates, by design, and refuses the provider while it is non-conformant.
  3. Mixed listing on the datasource_upload_mirror_test.go seam: only matching keys are enqueued, the fetch log shows an excluded key never read, and the closing listing names exactly the matches. The source's cap is 3 against four listed keys, three of them matches, so an excluded key that counted against the cap would leave the listing incomplete. TestWindow pins the same rule at the connector: the cap counts only admitted keys, and a cut is reported.
  4. Narrowing: UpdateSource with a tighter exclude queues a re-sync, and its closing listing drops exactly the newly excluded key and nothing else. A later edit against the old scope is refused as ErrDatasourceSourceScopeChanged.
  5. Kit: connect-object-storage-form.test.tsx covers patterns and file types submitted intact (a pattern containing {1,3} survives), a host refusal shown under the field it names and not repeated, and an unplaced refusal kept in the form's slot.

Each guard was checked by mutation, and each mutation turned a test red: no filter passed at sync (3/4), exclude no longer winning (1/3), Window counting before matching (TestWindow and test 3), no expected check on an upload edit (4), and the kit's refusal parser not recognising fields (5).

Reuse

  • Reused: GitHub's file filter and normalizeFileExtensions (the matcher sits beside them in datasource_file_filter.go), the GitHub connector's SourceConfig.InScope shape (the host builds the predicate, the connector applies it), the BadRequest refusal precedent in source_operation_invoke.go, UpdateSource's lock/expected/audit structure and its store write (the config JSONB already holds the upload config, so there is no migration), enqueuePullSync for the re-sync (it now returns the job id), and the GitHub form's collection field.
  • Deliberately not: the GitHub reconcile (datasource.github.reconcile), which is the wrong engine for a pull source; and generated TypeScript error-detail types, a larger SDK change than this needs while the message carries the field.

Fix or hack

A fix at the owner: the filter is per-source host config, applied by the connector to the listing. Nothing in it is transitional. objectstore/client.go, the engine #1211 deletes, holds neither the filter nor the cap; it only lists. runUploadSync is the one call site that changes when the envelope connector replaces it, and Scope goes with it unchanged.

Generated code

Proto, Go, the frontend _pb.ts, the catalogs and the exported contracts come from CI's pinned codefly (source 5234ce24). The TypeScript SDK comes from codefly 0.1.191, the release that generated main's copy. For each tool, the no-change control on clean main came back clean first. Regenerating the SDK with CI's pin instead rewrites library.codefly.yaml's generated-by stamp on unchanged input, so I did not use it for the SDK. generate contracts --check and the module-package run-generators drift gate pass, and only the contract digests moved in module.package.codefly.yaml.

Verified

  • Full accounts suite (go test -p 1 ./..., DB packages included), rerun after the move into Scope: 50 packages passed in the run, and pkg/auth/pg, whose harness failed to reach the codefly socket before any test ran, passed when rerun alone. The Scope mutations (count before filter; no InScope at sync) each turn TestWindow and test 3 red.
  • The generated-pins, interface-docs, authz-coverage, naming and boundary gates.
  • Kit: tsc, ESLint and its 350 tests. Portal: tsc, the type-primitive and inventory gates, and the full pure project (4,893 tests in 345 files).
  • kit-version.mjs check passes on this branch merged into current main, which contains the v0.0.105 tag. My earlier "rides main's unreleased versions" was checked against a base that predated that tag, and CI's red on "Frontend kit version" / "Release gates" was that.

Not verified

  • Live under codefly run. Admission refuses new object-storage sources and the dev fixture seeds none, so no source exists to filter. The behaviour is proven on the sync seam and the service tests only.
  • The real Postgres round trip of the patterns in the config JSONB. It goes through the existing datasourceConfigJSON/scan of the struct, and I did not add a DB test for it.
  • RE2 dialect parity in the browser. The kit checks only count and length, and the host is authoritative.

Size

About 875 net non-test lines against the merge base, counting docs, the 73-line proto change and the version bumps, and excluding generated files, the lockfile and ui-inventory.json. About 330 of those are the object-storage form and collection field moved here from #1214, so this PR's own work is about 545. Tests add about 500. Moving the filter into the connector added about 30 lines net: scope.go (50) against the cap and match it took out of client.go. That is just over the ~500 budget. I kept the kit test with the PR that needs it rather than split it off.

Merge order

This PR and #1214 add kit lines at the same anchors (a client call, a hook, the export lists, the README hooks line). Whichever lands second needs a trivial rebase. #1213 is independent of this PR, and the two touch disjoint files in objectstore. Whichever of them lands second gives #1213's FilesConnector this Scope in its SourceConfig, applied in Changes before the listing is digested. That is a few lines of wiring, and the filter itself does not move.

Docs

module/services/accounts/AGENTS.md gains "An object-storage source is a prefix and a key filter", which says the connector's Scope applies the filter, not the listing source. The kit README gains the form, objectStorageInputFrom and useAddObjectStorageSource. The proto comments state the semantics and the refusal shape.

🤖 Generated with Claude Code

… 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 #1211

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…Scope, not the listing client's

The owner's review of #1216: the match sat inside objectstore/client.go's
List, the file #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 #1211

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@antoinetoussaint-byte

Copy link
Copy Markdown
Contributor Author

Closing as finished work with no remaining home: #1235 contains this branch's head commit outright.

315dd360 (#1235's head) begins with 413084a9 (#1213's head) and 4762aa8d (#1216's head), followed by 264f6d69 Merge remote-tracking branch 'origin/issue-1211-bucket-key-patterns'. GitHub's compare confirms it: 4762aa8d...315dd360 is behind_by=0, status=ahead, i.e. nothing here is missing from there.

So this needs no rebase and no review. Rebasing it onto main — it is 22 commits behind — would re-run a full strict-main CI cycle on commits that already sit in a newer, green PR. #1235 is the one self-contained PR for issue #1211 and is where the review is happening.

Reopen if that compare ever stops being true.

🤖 Generated with Claude Code

crisfiorini-oss pushed a commit to crisfiorini-oss/module-saas-starter that referenced this pull request Oct 27, 2026
…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>
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.

1 participant