Skip to content

GV-14: Add BillingAccount storage admission and external-contribution limits - #700

Open
ScottArbeit wants to merge 4 commits into
epic/638-visibility-owned-branchesfrom
agent/653-content-ownership-ledger
Open

GV-14: Add BillingAccount storage admission and external-contribution limits#700
ScottArbeit wants to merge 4 commits into
epic/638-visibility-owned-branchesfrom
agent/653-content-ownership-ledger

Conversation

@ScottArbeit

@ScottArbeit ScottArbeit commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Pull Request

Linked Issue

Related to #653 and parent epic #638.

Summary

This pull request is being revised in place to replace its rejected contributor payer ledger with the approved
BillingAccount storage-admission tracer bullet.

The approved replacement model is:

  • One BillingAccount pays for all retained repository storage, including private branches.
  • AllowExternalContributions controls private contributions from users without repository write access.
  • External contributions receive a protected byte and active-branch sub-budget within total account capacity.
  • AllowsLargeFiles becomes a bounded server-enforced file-size tier: one MiB when disabled and a finite configured
    maximum when enabled.
  • Capacity is reserved before upload credentials, remains unattached through upload finalization, settles when the
    durable reference event produced by the composite command first retains the finalized upload, reclassifies without a
    total-byte change on promotion, and releases on rejection, abandonment, expiry, or logical deletion.
  • Branch creation is metadata-only and performs no upload settlement or blob-storage accounting.
  • This slice enforces bytes, file size, and branch count only. Request-rate controls are separate.

Current head 0b965fa2a841ef8654188c005df4db6c97f50a56 removes the superseded ContentOwnershipLedger implementation and
implements the approved BillingAccount storage-admission/event-properties replacement.

Scope

In scope:

  • Remove current payer-ledger contracts, actor state, registration/storage mapping, tests, and promotion/reference
    hooks.
  • Add the narrow BillingAccount aggregate and external storage-admission lifecycle specified by GV-14: Add BillingAccount storage admission and external-contribution limits #653.
  • Enforce AllowExternalContributions and existing AllowsLargeFiles at the appropriate server boundaries.
  • Prove atomic reservation, settlement, promotion reclassification, logical release, idempotency, concurrency, and the
    repository-create regression.

Out of scope:

  • Egress policy or public-delivery suspension.
  • Final prices, payment collection, or a broader public billing contract.
  • Request-rate limiting and request/concurrency quotas.
  • A complete temporary-resource accounting policy.

Validation Status

  • Targeted Fantomas: completed.
  • git diff --check: passed.
  • BillingAccount, file-size, and property-security focused tests: 16/16 passed.
  • Manifest-upload SDK and mixed whole-file/manifest tests: 20/20 passed.
  • Focused Grace Server and CLI Release builds: passed with zero warnings and errors.
  • OpenAPI bundle/projection and SDK generator matrix: regenerated; issue-specific freshness passes.
  • pwsh ./scripts/validate.ps1 -Fast: complete solution build passed with zero warnings and errors; CLI 697/697, Types
    174/174, Authorization 53/53, and Operations 26/26 passed. Server.Unit did not finish cleanly in the worker shell
    because existing static server modules initialized with missing storage configuration and then a null logger factory.
  • /repository/create: current-head CI created the owner, organization, and three repositories successfully. The old
    timeout is fixed.
  • Current-head GitHub Actions: Validate / full run 29148036443, job 86532826457, failed after build and fixture
    setup. Grace.Server.Tests passed 257/259; failures are BillingAccount reservation operation-id collision and a
    large-binary test that did not enable AllowsLargeFiles. Grace.Server.Unit.Tests had 31 static initialization failures
    because the unit-test process had no storage account key.
  • Required merge gate: green current-head Validate / full, explicit /repository/create proof from that run, and a
    fresh current-head Codex Code Review Bot result.

Review Status

  • Previous review state: three substantive high-risk cycles on the ContentOwnershipLedger payer/replay invariant
    family, followed by a maintainer hard stop.
  • Maintainer decision: the payer-ledger model is rejected and replaced by BillingAccount-owned repository storage.
  • Old current-head review threads remain historical evidence but are superseded as fix requirements when their entire
    implementation is removed. Do not resolve them as individually fixed before replacement code is pushed.
  • Maintainer corrections: branch creation is not a storage boundary; the old multi-order question is withdrawn; special
    permission-change accounting is rejected; settlement recovery is client-retry-only; usage meters logical finalized
    bytes even when physical content is deduplicated.
  • Maintainer resolution: any composite command that uploads content carries the canonical UploadSessionIds value in
    the resulting event's generic Properties, not in a primary named field on Branch.Save, Commit, Checkpoint,
    FileVersion, DirectoryVersion, or each command-specific DTO/event.
  • Transport decision: add one generic, server-allow-listed Properties dictionary to common command parameters; reject
    attempts to set protected server-owned metadata; canonicalize and validate UploadSessionIds before actor invocation.
  • Replacement implementation: committed and pushed as 0b965fa2a841ef8654188c005df4db6c97f50a56; worktree is
    clean and remote-aligned.
  • Current-head Codex state: review 4677429660 completed on 0b965fa2 with nine fresh findings.
  • Current-head CI state: failed as classified above; /repository/create regression is green.
  • Current stop: coding is stopped for maintainer approval. The fresh review shows that optional client
    UploadSessionIds cannot be the billing source of truth, post-event settlement can retain unaccounted content, and
    logical release/promotion/branch-slot lifecycle requires a server-owned retention invariant.
  • Proposed stabilization: server-computed Merkle root delta plus a durable idempotent pre-event RetentionClaim; see PR
    and issue comment 653-retention-stabilization-stop.md content and the local unblock page.
  • No fix worker may be assigned until the maintainer approves or revises the retained-storage model. Stale payer-ledger
    threads remain superseded.
  • Local decision page:
    C:\Users\scott\AppData\Local\Temp\grace-638-orchestration\grace-653-unblock-current.html.
  • Manual review trigger: do not trigger while the latest head already has a current-head Codex acknowledgement or
    result.

Review/Fix Prevention

The revised issue names the missing invariant family directly: storage admission belongs to a BillingAccount aggregate,
not a per-file payer-transfer ledger. The implementation must use bounded aggregate reads and atomic updates; it must
not scan content history or recreate payer-transfer semantics under another name.

Egress Deferral

Storage exhaustion does not by itself bound public download cost. That concern is explicitly deferred for a separate
design session. This worker must not mutate durable repository visibility at cutoff. A future design must cover a
distinct public-delivery state, anonymous routes, issued SAS URLs, caches/CDNs, authorized access, recovery, and the
relationship between storage and egress budgets.

Docs Impact

Update nearby documentation only where this tracer bullet changes a currently documented repository setting or upload
behavior. Pricing, payment, and egress documentation remain deferred.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

CI failure routed to fresh validation worker

GitHub Actions Validate / full failed for current head 1fb153740a0e3783f6152d85285c8e7a628828ee in run 29016904483, job 86114038044.

The build step succeeded. The failure is in Grace.Server.Tests shared setup:

  • Aspire resources reported healthy.
  • Shared fixture setup reached repository creation.
  • /repository/create returned 500 because RepositoryActor.Handle(RepositoryCommand, EventMetadata) did not respond within 60 seconds.
  • The remaining Grace.Server.Tests failures cascade from OneTimeSetUp failing; CI reported Failed: 255, Passed: 4, Total: 259 for Grace.Server.Tests.

Representative error:

Response did not arrive on time in 00:01:00 for message ... repositoryactor/... IRepositoryActor.Handle(Grace.Types.Repository+RepositoryCommand, Grace.Types.Common+EventMetadata)
Path: /repository/create

I assigned fresh validation-fix worker Avicenna (019f46ca-a32e-7392-8e9e-5d09029abeb8) on C:\Source\Grace-gh-653 to determine whether the #653 ledger changes introduced a repository actor timeout/dependency cycle or whether this is a CI/runtime flake. The worker is scoped to #653 and will either push a minimal fix with focused proof plus pwsh ./scripts/validate.ps1 -Fast, or report no-code flake evidence for rerun.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1fb153740a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Grace.Actors/Reference.Actor.fs Outdated
Comment thread src/Grace.Actors/PromotionSet.Actor.fs Outdated
Comment thread src/Grace.Actors/ContentOwnershipLedger.Actor.fs Outdated
Comment thread src/Grace.Actors/ContentOwnershipLedger.Actor.fs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if existingEvents
|> Seq.exists (fun event -> event.Metadata.CorrelationId = eventMetadata.CorrelationId) then
Error(GraceError.Create "Duplicate correlation ID for PromotionSet command." eventMetadata.CorrelationId)

P2 Badge Allow same-correlation apply transfer retries

If TransferAcceptedContentOwnershipForSteps fails after the Applied event has been persisted, retrying the same apply request with the same correlation id is rejected here before the new succeeded-state transfer retry path can run. That leaves recovery dependent on callers inventing a new correlation id, even though this change explicitly relies on re-entering Apply after success to finish or replay the ownership transfer.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Grace.Actors/ContentOwnershipLedger.Actor.fs Outdated
Comment thread src/Grace.Types/ContentOwnershipLedger.Types.fs Outdated
Comment thread src/Grace.Actors/Reference.Actor.fs Outdated
Comment thread src/Grace.Actors/Reference.Actor.fs Outdated
@ScottArbeit

Copy link
Copy Markdown
Owner Author

Second-cycle stabilization pass: content ownership ledger durability and replay closure

PR #700 received a fresh current-head Codex review on 429bf6a3f3ce431f7258ea561ddb2d5f7b6f6d92 after the first CI/review fix pass was pushed and the earlier four review threads were resolved.

Because this PR touches actor behavior, storage, persisted state, retries, idempotency, and side-effect ordering, this is the second substantive high-risk review cycle. I am starting the stabilization pass before assigning another worker.

Review timeline

  • Initial PR head 1fb153740a0e3783f6152d85285c8e7a628828ee opened GV-14: Add BillingAccount storage admission and external-contribution limits #653 implementation.
  • GitHub Actions run 29016904483 failed in Grace.Server.Tests setup because /repository/create timed out inside RepositoryActor.Handle.
  • Codex review 4662865172 on 1fb15374 found four ledger lifecycle/retry findings.
  • Worker Avicenna fixed the CI timeout and first review findings in 429bf6a3f3ce431f7258ea561ddb2d5f7b6f6d92; local focused reproduction and pwsh ./scripts/validate.ps1 -Fast passed.
  • The four first-cycle review threads were replied to and resolved.
  • Current-head Codex review 4663040981 on 429bf6a3 found four new findings:
    • PRRT_kwDOILVrb86PkV7H: missing Cosmos partition mapping for ContentOwnershipLedger state.
    • PRRT_kwDOILVrb86PkV7N: accepted ownership can be lost when all active refs are removed and the manifest is later reused.
    • PRRT_kwDOILVrb86PkV7T: idempotent create replay can use retry metadata instead of persisted reference owner scope.
    • PRRT_kwDOILVrb86PkV7X: promotion references were excluded from retention accounting even though ownership accounting now treats them as active.

Missing invariant family

The ledger needs durability/replay closure across every place where ownership state crosses an actor or storage boundary:

  • The new ledger state must be persistable in the hosted Cosmos-backed actor environment, not only in unit tests.
  • Accepted repository ownership is a durable content ownership fact, not merely a conclusion derived from currently active repository-owned refs.
  • Reference create replay must use the durable owner scope already persisted on the reference, not transient retry metadata.
  • Ownership accounting and retention accounting must not diverge for promotion references that keep manifest-backed content alive.
  • Apply retry after Applied persistence must be able to finish or replay accepted ownership transfer with the same correlation id used by the original apply attempt.

Proof obligation for the stabilization worker

The next worker must keep scope to #653 and prove:

  • StateName.ContentOwnershipLedger has a hosted storage partition mapping that matches the actor key and does not break existing actor state partitioning.
  • Accepted transfer leaves durable evidence that later contributor-owned reuse of the same manifest remains repository-owned even after all active refs were removed.
  • Retried create paths derive ledger owner scope from persisted ReferenceDto state where a reference already exists.
  • Promotion references that make ownership ledger entries also participate correctly in repository retention/range accounting, or an existing retention path is proven to cover them without divergence.
  • Retrying apply with the same correlation id after Applied persistence can finish or replay the accepted ownership transfer path.
  • Focused tests cover these cases, followed by targeted Fantomas, focused proof tests, pwsh ./scripts/validate.ps1 -Fast, git diff --check, and the PR-associated CI / current-head Codex gate.

No maintainer product decision is required for this pass because the findings refine #653's already-accepted ledger, transfer, persisted-state, retry, and retention responsibilities. If the worker discovers that a new public contract, durable domain concept beyond ContentOwnershipLedger, or broader retention redesign is required, they must stop and report the missing decision instead of widening the patch.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Second-cycle stabilization fix pushed

The PR #700 second-cycle stabilization fix was pushed as aa8960038876e5f1b5ffea3fbcaa3f53c59a0e58.

Resolved current-head review threads from Codex review 4663040981:

  • PRRT_kwDOILVrb86PkV7H: added hosted partition mapping for StateName.ContentOwnershipLedger.
  • PRRT_kwDOILVrb86PkV7N: preserved accepted repository ownership even after all active refs are removed and the manifest is later reused.
  • PRRT_kwDOILVrb86PkV7T: create replay now derives ledger owner scope from persisted ReferenceDto state.
  • PRRT_kwDOILVrb86PkV7X: promotion references now participate in retention accounting as well as ownership accounting.

The top-level review-body finding, Allow same-correlation apply transfer retries, is also fixed in aa896003: duplicate correlation ids remain rejected generally, but a same-correlation Apply retry is allowed after the PromotionSet is already Succeeded, so accepted ownership transfer can finish or replay after Applied was persisted.

Validation evidence from Halley:

  • Targeted Fantomas passed.
  • dotnet build --configuration Release src/Grace.Server.Unit.Tests/Grace.Server.Unit.Tests.fsproj passed.
  • Focused Server.Unit tests passed, 57/57.
  • pwsh ./scripts/validate.ps1 -Fast passed.
  • git diff --check passed.

The earlier current-head Validate run 29018307199 failed before any steps because GitHub could not acquire a hosted runner. A new PR-associated Validate run exists for aa896003 and is the current CI gate.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

CI failure routed to fresh validation worker

GitHub Actions Validate / full ran for current head aa8960038876e5f1b5ffea3fbcaa3f53c59a0e58 in run 29019558132, job 86122578146, and failed in the Test step.

This is not the hosted-runner acquisition failure seen on run 29018307199, and it is not a missing check pickup. The check started normally, completed restore/build, started Grace.Server.Tests, and failed during shared setup:

  • Aspire resources reported healthy.
  • Shared fixture setup reached repository creation.
  • /repository/create returned 500 because RepositoryActor.Handle(RepositoryCommand, EventMetadata) did not respond within 60 seconds.
  • The remaining Grace.Server.Tests failures cascade from OneTimeSetUp failing; CI reported Failed: 255, Passed: 4, Total: 259 for Grace.Server.Tests.

Representative error:

Response did not arrive on time in 00:01:00 for message ... repositoryactor/... IRepositoryActor.Handle(Grace.Types.Repository+RepositoryCommand, Grace.Types.Common+EventMetadata)
Path: /repository/create

I assigned fresh validation-fix worker Newton (019f46fa-895d-7423-952c-d5fc6628f9dd) on C:\Source\Grace-gh-653 to diagnose why the repository-create timeout recurred after the second-cycle stabilization commit. The worker is scoped to #653 content ownership ledger / accepted ownership transfer / reference replay / repository create behavior, with an explicit stop condition if the fix requires a new public contract, new durable state concept beyond the existing ContentOwnershipLedger responsibilities, new retry policy outside #653, or a product decision outside the accepted ledger/replay/retention invariants.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa89600388

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

(metadata: EventMetadata)
: Result<ContentOwnershipLedgerDecision, GraceError>
=
let command = commandWithDurableAcceptedOwner events command

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve original payload when replaying add operations

Fresh evidence beyond the earlier Reference metadata replay issue: this rewrites contributor-owned add commands before tryFindAppliedOperation checks the operation id. After an accepted transfer exists, retrying the original reference:<id>:... add for a reference that was first recorded as contributor-owned is rewritten to repository-owned, so the existing ActiveUsageAdded event no longer matches and the otherwise idempotent Reference create replay is rejected as a different payload. Only apply the accepted-owner rewrite after confirming the add operation id has not already been persisted.

Useful? React with 👍 / 👎.

command
| _ -> command

let decideCommandForKey

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document the ledger decision entry point

src/AGENTS.md requires new F# modules, types, functions, methods, members, and meaningful helper functions to have concise /// XML documentation. This new exported module function is the primary-key-scoped decision entry point used by the actor and tests, but it is missing the required XML doc while the wrapper below is documented, so future maintainers lose the intended invariant at the public boundary.

Useful? React with 👍 / 👎.

Comment on lines +208 to +212
| ContentOwnershipOwnerScope.ContributorOwned _ when
activeUsageOwners.Values
|> Seq.contains repositoryOwnerScope
->
repositoryOwnerScope

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep private reuse contributor-owned until acceptance

When any repository-owned active usage already exists for the manifest, this branch converts every new contributor-owned add into repository-owned usage even if there is no accepted transfer for that contributor. That violates docs/Contributor-owned accounting design.md lines 66-67, where abandoned/rejected private work remains contributor-owned and same-content contributor scopes stay separate; for example, a private save that reuses content already present on a repository-owned branch would be billed to the repository without being accepted.

Useful? React with 👍 / 👎.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Validation worker assignment could not start

The attempted fresh validation-fix worker Newton (019f46fa-895d-7423-952c-d5fc6628f9dd) failed immediately because the Codex usage limit was reached. No code work began and no branch changes were made by that worker.

Current actionable state remains:

  • PR GV-14: Add BillingAccount storage admission and external-contribution limits #700 head: aa8960038876e5f1b5ffea3fbcaa3f53c59a0e58.
  • GitHub Actions Validate / full run 29019558132, job 86122578146, failed in Grace.Server.Tests shared setup.
  • Failure family: /repository/create timed out inside RepositoryActor.Handle(...) after 60 seconds, cascading to 255 server test failures.
  • This is a real validation failure, not a missing check pickup or hosted-runner acquisition failure.

A fresh validation-fix worker still needs to be assigned when Codex worker capacity is available, or the maintainer needs to explicitly approve the main orchestrator deviating from the Grace rule that coding/fix work is delegated to fresh workers.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Current-head hard stop: ledger replay and owner-normalization ordering

PR #700 received a fresh current-head Codex review on aa8960038876e5f1b5ffea3fbcaa3f53c59a0e58 after the second-cycle stabilization pass was pushed, local validation passed, and the previous current-head review threads were replied to and resolved.

This PR is now stopped under the repeated-review rules. The content ownership ledger is a high-risk actor, storage, retry, idempotency, persisted-shape, and side-effect-ordering surface. The latest review contains another substantive ledger/retry finding and another substantive billing-ownership finding, so this is not routine patch churn.

Current state

  • Current PR: GV-14: Add BillingAccount storage admission and external-contribution limits #700.
  • Current head: aa8960038876e5f1b5ffea3fbcaa3f53c59a0e58.
  • GitHub Actions Validate / full: failed in run 29019558132, job 86122578146.
  • CI failure family: Grace.Server.Tests shared setup reached /repository/create, then RepositoryActor.Handle(RepositoryCommand, EventMetadata) timed out after 60 seconds. The remaining 255 server-test failures cascade from OneTimeSetUp failing.
  • Previous current-head review threads addressed and resolved: PRRT_kwDOILVrb86PkV7H, PRRT_kwDOILVrb86PkV7N, PRRT_kwDOILVrb86PkV7T, and PRRT_kwDOILVrb86PkV7X.
  • Fresh current-head review threads now blocking another worker:
    • PRRT_kwDOILVrb86Pk06D: preserve the original payload when replaying add operations.
    • PRRT_kwDOILVrb86Pk06I: add the required XML documentation for the ledger decision entry point.
    • PRRT_kwDOILVrb86Pk06N: keep ordinary private reuse contributor-owned until acceptance.

Review timeline

  • Initial PR head 1fb153740a0e3783f6152d85285c8e7a628828ee opened the GV-14: Add BillingAccount storage admission and external-contribution limits #653 implementation.
  • CI run 29016904483 failed in Grace.Server.Tests setup because /repository/create timed out inside RepositoryActor.Handle.
  • Codex review 4662865172 on 1fb15374 found four ledger lifecycle/retry findings.
  • Worker Avicenna fixed the CI timeout and first review findings in 429bf6a3f3ce431f7258ea561ddb2d5f7b6f6d92; local focused reproduction and pwsh ./scripts/validate.ps1 -Fast passed.
  • Codex review 4663040981 on 429bf6a3 found a second high-risk ledger durability/replay set.
  • Worker Halley fixed that second-cycle set in aa8960038876e5f1b5ffea3fbcaa3f53c59a0e58; targeted Fantomas, focused Server.Unit tests, pwsh ./scripts/validate.ps1 -Fast, and git diff --check passed locally.
  • PR-associated CI on aa896003 failed again in /repository/create setup.
  • Codex review on aa896003 found the new replay/ownership ordering findings listed above.

Suspected missing invariant family

The ledger needs a clearer separation between three ideas that currently overlap in the implementation:

  • Durable operation replay: if Grace already recorded an operation id and payload, a retry should replay that original decision before applying newer owner-normalization rules.
  • Accepted-transfer memory: after Grace accepts manifest-backed contributor content, that accepted-transfer fact can be durable and can shape future new add operations for that manifest.
  • Ordinary repository reuse: a private contributor saving bytes that the repository already has is not the same thing as accepting that contributor's private work. Repository-owned active usage alone should not silently transfer a new private usage to repository billing unless the maintainer chooses that product behavior.

The CI timeout may be another symptom of the same actor/replay/ledger boundary. The next approved pass should prove the ledger rules and the failing server setup path together unless the maintainer explicitly wants those split.

Maintainer decisions needed

I generated a local unblock page with longer explanations and clickable choices:

C:\Users\scott\AppData\Local\Temp\grace-638-orchestration\grace-653-unblock-current.html

Please approve or revise these decisions before another #653 coding/fix worker is assigned:

  1. Retry ordering for ledger adds:
    Recommended default: if an add operation id already exists, Grace replays the original persisted add decision before applying accepted-transfer owner rewriting. Accepted-transfer rewriting applies only to new add operations.

  2. Ordinary private reuse of repository-owned bytes:
    Recommended default: private reuse stays contributor-owned until accepted transfer covers that manifest-backed content. Repository-owned active usage alone is not an ownership-transfer trigger.

  3. Durable accepted-transfer memory:
    Recommended default: accepted-transfer history is durable for future new adds of the same manifest-backed content, even after active references are removed. Existing operation retries still replay original persisted payloads first.

  4. Next-worker scope:
    Recommended default: the next GV-14: Add BillingAccount storage admission and external-contribution limits #653 stabilization worker owns both the approved current-head review findings and the recurring /repository/create CI timeout in one scoped pass.

Proof obligation for the approved fix

  • Retrying an existing ledger add operation id replays the original persisted payload before any owner-normalization rewrite.
  • A brand-new add can use durable accepted-transfer history to become repository-owned when that history exists for the same manifest-backed content.
  • A private save or promotion that merely reuses repository-owned bytes remains contributor-owned until acceptance.
  • The ledger decision entry point has the required XML documentation and explains the replay/normalization invariant.
  • The recurring /repository/create timeout is fixed or proven unrelated with focused server setup evidence.
  • Focused tests cover replay-before-rewrite, ordinary private reuse, accepted-transfer future reuse, and the server setup failure family.
  • The worker runs targeted formatting, focused proof tests, pwsh ./scripts/validate.ps1 -Fast as the final local build/test gate, git diff --check, and then pushes for current-head CI and Codex Code Review Bot review.

No additional #653 coding or fix worker should be assigned until the maintainer approves or revises these decisions. Also, the attempted fresh worker Newton failed immediately because the Codex usage limit was reached, so no code work began after aa896003.

@ScottArbeit ScottArbeit changed the title GV-14: Implement contributor-owned accounting transfer GV-14: Add BillingAccount storage admission and external-contribution limits Jul 10, 2026
@ScottArbeit

Copy link
Copy Markdown
Owner Author

Maintainer Approval: Replace The Payer Ledger

The maintainer approved replacing #653 and PR #700's contributor payer ledger with the revised BillingAccount
storage-admission tracer bullet now recorded in the issue body.

Accepted decisions:

  • BillingAccount owns all repository storage, including private branches.
  • AllowExternalContributions controls users without repository write access.
  • Temporary resources remain separate.
  • Free accounts receive one pooled GiB.
  • Zero remaining capacity immediately stops storage growth.
  • Automatic add-ons require a spending cap; prices and payment collection remain deferred.
  • External contribution capacity is a configurable percentage of included and purchased storage, bounded by plan
    minimums and maximums and starting near ten percent.
  • AllowsLargeFiles = false enforces a one-MiB maximum. true enables a larger but still finite configured maximum.
    Manifest eligibility remains separate.
  • This slice admits on bytes, file size, and active external branch count only. Request controls belong to a separate
    rate limiter.
  • Capacity is reserved before upload credentials, settled when the external branch is created, reclassified without
    changing total storage on promotion, and released logically on rejection, abandonment, expiry, or deletion.

The three prior substantive review cycles belong to the superseded ContentOwnershipLedger invariant family. Their
timeline remains recorded on PR #700, but the next worker must remove that model rather than continue one-off fixes.

Egress remains deferred. The newly recorded concern is that a public repository can continue generating anonymous
download cost after storage growth is cut off. This worker must not convert durable repository visibility at cutoff. A
future design session must decide whether Grace needs a separate public-delivery-suspended state and cover anonymous
routes, issued SAS URLs, caches/CDNs, authorized access, recovery, and storage-versus-egress budget semantics.

One fresh GPT-5.6-Sol Medium worker is approved within the revised issue scope.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Replacement Worker Assigned

Fresh worker Ohm (019f4e00-c8cf-78d1-99ee-429e8451afff) is assigned using GPT-5.6-Sol Medium.

The worker owns replacement of the current payer ledger with the revised #653 BillingAccount storage-admission tracer
bullet on agent/653-content-ownership-ledger. The assignment requires full old-ledger removal, bounded aggregate
admission, reserve-before-credentials, settlement/reclassification/release lifecycle proof, AllowsLargeFiles and
AllowExternalContributions enforcement, repository-create regression proof, Fast validation, commit, and push.

The worker is explicitly prohibited from implementing egress/public-delivery policy, mutating durable repository
visibility at cutoff, adding payment/final-pricing contracts, adding request-rate limits, or broadening temporary-resource
accounting.

Status file: %TEMP%\grace-agent-status\653-billing-account-storage-admission.md.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Coding Stop: Reservation-To-Branch Binding Needs Approval

Fresh worker Ohm stopped before commit or push. The rejected payer ledger is inverted in the local #653 worktree, and a
small uncommitted BillingAccount aggregate has five passing focused tests, but the replacement is not connected to a
server route and is not reviewable.

Code-grounded blocker:

  • Upload sessions already have a durable UploadSessionId, repository identity, one-file path scope, expected size,
    file hash, lifecycle state, and expiry/abandonment transitions.
  • Upload start does not identify the future BranchId.
  • Branch creation does not carry an upload-session or reservation identifier.
  • Reference creation also has no upload-session evidence.
  • Matching by path, hash, manifest, or repository history would be ambiguous and would violate the approved constant-time
    admission requirement.

The smallest candidate binding is to add the future BranchId to upload-session start, keep UploadSessionId as the
reservation identity, and group reservations by BranchId. That changes the public upload-start SDK/OpenAPI request but
does not require another identifier on branch creation.

Additional decisions are required before a continuation worker can safely wire the lifecycle:

  1. Whether upload start declares BranchId or branch creation submits reservation identifiers.
  2. How per-file reservations settle when finalization and branch creation can happen in either order.
  3. How permission changes reclassify or reject a reservation before settlement.
  4. Whether source actors durably record and retry BillingAccount side effects after saving their own lifecycle event.
  5. Whether the included-storage meter counts logical finalized bytes or deduplicated physical/unique content.

Local decision page:

C:\Users\scott\AppData\Local\Temp\grace-638-orchestration\grace-653-unblock-current.html

No replacement commit was created, no push occurred, Fast validation was intentionally not run, and the PR remains at
aa8960038876e5f1b5ffea3fbcaa3f53c59a0e58. Coding remains stopped until the maintainer approves the binding and
recovery packet. After approval, use one fresh GPT-5.6-Sol Medium continuation worker in the existing worktree and
preserve the uncommitted replacement work unless the approved decisions require changing it.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Maintainer Correction: Branch Creation Is Not A Storage Boundary

The previous unblock packet incorrectly treated branch creation as the event that settles upload reservations. The
maintainer rejected that premise.

The code-grounded lifecycle is:

  1. The branch already exists.
  2. An upload session reserves bytes and finalizes a manifest.
  3. The SDK constructs a manifest-backed FileVersion locally.
  4. The client persists directory versions containing the new file version.
  5. Branch.Save creates a durable Save reference pointing the existing branch at the new root directory version.

Upload finalization proves valid content but remains unattached. Persisting a detached directory graph also does not
make the content branch history. The durable Save reference is the retained-content boundary. Creating a branch is
metadata-only and incurs no blob-storage accounting.

Maintainer dispositions:

  • Old Q1 premise: rejected and corrected as above.
  • Old Q2: withdrawn because it was based on the same incorrect branch-creation lifecycle.
  • Old Q3: rejected as an irrelevant finding. Do not add special permission-change accounting or reclassification
    machinery. Normal request authorization still applies.
  • Q4B: if the Save reference commits but BillingAccount settlement fails, repair only when the client retries the same
    idempotent Branch.Save operation. Do not add a durable background retry mechanism for this rare failure.
  • Q5A: meter logical finalized bytes for retained uploads even when physical content is deduplicated. Physical
    deduplication remains an internal cost optimization.

One contract decision remains. Branch.Save must provide validated evidence linking the finalized upload reservations
to manifests in the saved root. The corrected local unblock page presents only that decision:

C:\Users\scott\AppData\Local\Temp\grace-638-orchestration\grace-653-unblock-current.html

Coding remains stopped until that binding is approved. The recommended shape is for Branch.Save to submit the relevant
UploadSessionId values, which the server validates against finalized manifests in the root graph it already traverses.
This avoids putting temporary upload provenance into immutable FileVersion identity.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Maintainer Resolution: Upload Evidence Belongs In Event Properties

The remaining reservation-binding question is resolved with a command-independent rule:

When executing a command causes file uploads, the resulting event may carry the relevant upload-session identifiers
in its Properties. Upload-session identity is not a primary named field on Branch.Save, Commit, Checkpoint,
FileVersion, DirectoryVersion, or each command-specific DTO/event.

Code-grounded implementation interpretation:

  • Existing EventMetadata.Properties is a persisted Dictionary<string,string> shared by domain events.
  • Add one generic Properties : Dictionary<string,string> transport to common public command parameters so the CLI,
    SDK, OpenAPI, and generated clients can supply approved event metadata without adding fields to every command.
  • Client properties are untrusted. The server allow-lists client-settable keys, rejects attempts to set or override
    repository identity, visibility, ownership, authorization, and other server-owned metadata, and canonicalizes values
    before actor invocation.
  • Use UploadSessionIds as the property key. Omit it for zero reservations. For one or more IDs, parse as GUIDs, reject
    malformed or duplicate semantic IDs, format lowercase N, sort ordinally, and join with commas without whitespace.
  • Preserve upload-session IDs from the shared manifest-upload result and propagate them through every composite workflow
    that uploaded before retaining a reference. The mapped paths include commit, checkpoint, save, tag, createExternal,
    promote, Watch, implicit Diff saves, branch-switch saves, and shared capture flows.
  • Commands that only reference already-retained content, or that uploaded no manifest-backed files, omit the property.
  • At the retaining-reference boundary, validate every supplied session against durable repository, lifecycle, path,
    finalized manifest address, and logical size evidence in the exact root graph already traversed for manifest retention.
  • Settle only after the retaining reference event is durable. If settlement then fails, rely on replay of the same
    idempotent command; do not add durable background repair machinery.
  • Count logical finalized bytes once retained, even when physical content is deduplicated.

This uses the existing event extensibility construct and does not add a new payer ledger, named per-command upload field,
FileVersion provenance field, or background retry policy. The #653 issue body and PR #700 review status have been updated.
One fresh GPT-5.6-Sol Medium continuation worker is approved to continue in the existing worktree and preserve the
uncommitted old-ledger inverse and BillingAccount prototype unless the revised contract requires changing them.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Event-Properties Continuation Worker Assigned

Fresh worker Mencius (019f5060-0746-7a81-8e33-308a6e39701b) is assigned using GPT-5.6-Sol Medium in the existing
issue 653 worktree and branch.

The worker must preserve the prior uncommitted old-ledger inverse and BillingAccount prototype, complete the revised
storage-admission tracer bullet, and implement the maintainer-approved generic event-properties rule across every actual
upload-before-reference workflow.

Primary prevention targets:

  • Do not add upload-session fields to individual command/event/content contracts.
  • Do not let client properties override server-owned metadata.
  • Do not omit commit, checkpoint, save, tag, createExternal, promote, Watch, implicit Diff saves, branch-switch saves,
    or shared capture paths when they uploaded content.
  • Do not add the property when a command uploaded nothing or only reused retained content.
  • Validate canonical session evidence against the exact retained root and durable upload state.
  • Settle after the durable retaining event and repair only through replay of the same client command.
  • Do not implement egress, background settlement repair, payer transfer, final pricing/payment, or request-rate policy.

Status file: %TEMP%\grace-agent-status\653-event-properties-continuation.md.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Fresh Continuation Worker Assigned

Worker Mencius completed an intermediate uncommitted slice but stopped without a product blocker and before server
integration, validation, commit, or push. Its changes remain preserved in the issue 653 worktree.

Fresh GPT-5.6-Sol Medium worker Avicenna (019f5069-6047-7db2-997c-e191fd03c14f) now owns completion of the approved
implementation. The worker was explicitly instructed not to stop at another intermediate status report and to continue
through property propagation, admission/settlement/release integration, generated artifacts, required proofs, Fast
validation, self-review, commit, and push unless it discovers a genuinely new product construct outside the live issue.

Status remains %TEMP%\grace-agent-status\653-event-properties-continuation.md.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

BillingAccount Replacement Pushed

The revised #653 implementation is committed and pushed on PR #700:

  • Commit: 0b965fa2a841ef8654188c005df4db6c97f50a56
  • Branch: agent/653-content-ownership-ledger
  • Worktree: clean and aligned with the remote branch
  • Commit message: Implement BillingAccount storage admission

Implemented replacement:

  • Removed the rejected ContentOwnershipLedger types, actor, tests, project entries, storage mapping, and
    Reference/PromotionSet hooks.
  • Added persistent owner-keyed BillingAccount actor state, replay evidence, total/external byte budgets, active external
    branch accounting, and idempotent reserve/settle/promote/release transitions.
  • Reserved full logical size immediately before the first block-upload credential is issued.
  • Enforced one-MiB standard files, a finite configurable large-file maximum, and durable
    AllowExternalContributions repository policy.
  • Added generic common-command Properties, strict server allow-listing, protected-property rejection, and canonical
    UploadSessionIds encoding.
  • Preserved upload IDs from actual manifest uploads and propagated them through direct reference commands, Watch, and
    shared current-state capture while omitting whole-file, no-upload, and reuse-only flows.
  • Validated upload-session repository, lifecycle, path, manifest address, and logical size against the retained root.
  • Settled only after the retaining event was durably accepted, with client replay as the only post-event repair path.
  • Added abandonment/expiry release and promotion reclassification without removing total retained bytes.
  • Regenerated OpenAPI projections and the generated SDK matrix.

Local evidence:

  • BillingAccount/file-size/property-security tests: 16/16 passed.
  • Manifest-upload/mixed-storage SDK tests: 20/20 passed.
  • Focused Server and CLI Release builds: passed with zero warnings/errors.
  • Targeted Fantomas and git diff --check: passed.
  • OpenAPI/SDK issue-specific freshness: passed.
  • Fast gate build: zero warnings/errors; CLI 697/697, Types 174/174, Authorization 53/53, Operations 26/26.

Local gate limitation:

  • Server.Unit did not finish cleanly in the worker shell because existing static server modules initialized first with
    missing storage configuration and then with a null logger factory. The issue-specific pure focused tests pass.
  • The old ledger callback behind the previous /repository/create timeout is gone, but a live repository-create
    regression was not independently completed locally.

Current blocking gates:

  • GitHub Validate / full run 29148036443, job 86532826457, is in progress and must prove /repository/create.
  • Codex acknowledged current head with 👀 and is reviewing. No manual trigger is allowed.

Residual implementation risks to evaluate against CI/review:

  • Promotion/release decisions are unit-proven, but every durable rejection/deletion integration is not yet proven end to
    end.
  • Reservations are intentionally branch-independent. Concurrent unattached external reservations may receive upload
    credentials before one later loses the external branch-slot race at settlement.
  • The 739-file diff is dominated by generated-client matrix propagation from adding generic common properties.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Current-Head CI Failure Classified

GitHub Validate / full run 29148036443, job 86532826457, completed on
0b965fa2a841ef8654188c005df4db6c97f50a56 and failed in the Test step.

What is now proven:

  • The complete solution restored and built.
  • Aspire resources became healthy.
  • Shared fixture setup created the owner, organization, and three repositories successfully.
  • The recurring /repository/create timeout is gone on this head.
  • Grace.Server.Tests reached and ran its test body: 257 passed and 2 failed.

Fresh failures:

  1. ContentBlockUploadUriScopesStagingKeyByRepository received
    BillingAccount operation id was reused with a different payload. The test's repeated upload-credential requests now
    collide under the reservation operation identity and need diagnosis against the approved upload-session replay
    invariant.
  2. LargeBinarySaveLifecycleRequiresFinalizedManifestAndDownloadsAfterRouteChange attempted 1,052,672 bytes while the
    repository retained the new default AllowsLargeFiles = false; the server correctly enforced the one-MiB limit.
    The test or setup must explicitly enable the approved finite large-file tier before exercising the large-binary path.
  3. Grace.Server.Unit.Tests failed 31 tests during static ApplicationContext initialization because the CI unit-test
    process had no storage account key. This is the same initialization family observed locally. It now blocks tests that
    touch Services or Storage helpers and must be fixed without requiring real Azure credentials for pure unit tests.

This is a fresh latest-head CI result against the replacement BillingAccount model, not a recurrence of the old payer
ledger review cycle. No fix worker is assigned yet because Codex has 👀 on the same head and the orchestration rules
require waiting for the completed latest-head review before forming the action set.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b965fa2a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -1626,6 +1626,9 @@ module Application =
route "/setAllowsLargeFiles" Repository.SetAllowsLargeFiles
|> addMetadata typeof<Repository.SetAllowsLargeFilesParameters>

route "/setAllowExternalContributions" Repository.SetAllowExternalContributions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate external-contribution toggles with repository permission

This new route is registered without requireRepositoryWriteOrAdmin or requireRepositoryAdmin; the endpoint manifest marks it only Authenticated, and the handler validations only check that the repository is not deleted. In a private repository, any authenticated caller who can name the repository can flip AllowExternalContributions, enabling the storage path that was supposed to stay owner-controlled.

Useful? React with 👍 / 👎.

match validateRepositoryExistsForStorageRequest session.RepositoryId repositoryDto correlationId with
| Error error -> return! context |> result400BadRequest error
| Ok () ->
match! reserveUploadCapacity context repositoryDto session correlationId with

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reserve storage before every retainable upload path

reserveUploadCapacity is only called from the content-block upload-URI path (checked repo-wide references), so retained uploads that never request a block URI—whole-file fallback and fully deduped manifest sessions—can finalize and be saved with no BillingAccount reservation or file-size admission. That lets repeated small/whole-file or deduped retained content bypass account capacity.

Useful? React with 👍 / 👎.

Comment on lines +996 to +998
let! settlement =
match tryGetRetainingDirectoryId cmd with
| Some directoryId -> settleRetainedUploadSessions graceIds correlationId directoryId retainedUploadSessions metadata

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not persist references before settlement can succeed

Settlement runs only after actorProxy.Handle has already accepted the retaining branch command, so a later BillingAccountCommand.Settle rejection leaves the save/promotion reference durable even though the request returns 500. For example, two external branches can both reserve while the branch limit has room; after the first settle consumes the last branch slot, the second settle fails, but its branch reference has already been written and retains content without settled branch accounting.

Useful? React with 👍 / 👎.

Comment on lines +230 to +231
billingAccount.Handle
(Grace.Types.BillingAccount.BillingAccountCommand.Settle(settlementId, reservationId, graceIds.RepositoryId, graceIds.BranchId))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Release settled reservations when references stop retaining

This settlement path creates retained BillingAccount usage, but the change adds no matching release for branch/reference logical deletion or save/checkpoint expiry; I checked all BillingAccountCommand.Release call sites and only upload-session abandon/expire invokes it. Once a saved reference with UploadSessionIds expires or is deleted, UsedBytes/ExternalBytes remain consumed permanently even though the content is no longer retained by that reference.

Useful? React with 👍 / 👎.

let private tryGetRetainingDirectoryId (command: BranchCommand) =
match command with
| BranchCommand.Assign (directoryId, _, _, _)
| BranchCommand.Promote (directoryId, _, _, _)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reclassify external reservations when promotions land

Promotion is treated as a retaining command here, but the settlement path only issues BillingAccountCommand.Settle, and a repo-wide search finds BillingAccountCommand.Promote only in unit tests. When an external branch's saved manifest is promoted to repository-owned usage, its reservation remains ExternalContribution, so ExternalBytes and ActiveExternalBranches stay consumed after acceptance instead of freeing the protected budget.

Useful? React with 👍 / 👎.

Comment on lines +66 to +68
elif storageClass = ExternalContribution
&& account.ActiveExternalBranches
>= account.ExternalBranchLimit then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow uploads to existing external branches at the limit

At reservation time the command has no retaining branch, so this check rejects every external upload whenever ActiveExternalBranches is at ExternalBranchLimit. That includes adding another file to an already-counted external branch—a case Settle handles with ExternalBranchReservationCounts without incrementing branch count—so a branch at the limit becomes unable to update even though it would not consume a new branch slot.

Useful? React with 👍 / 👎.

let private validateRetainedUploadSessions (graceIds: GraceIds) correlationId (properties: Dictionary<string, string>) command =
task {
match properties.TryGetValue UploadSessionIdsProperty with
| false, _ -> return Ok []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require session evidence for manifest-backed references

If a client omits UploadSessionIds, this branch accepts the retaining command with no settlement attempt. For a manifest upload that did reserve capacity, the saved reference can then retain content while ActiveExternalBranches never increments, so custom or older clients can bypass the external branch sub-budget simply by leaving out the property.

Useful? React with 👍 / 👎.

Comment on lines +59 to +62
static member public SetAllowExternalContributions(parameters: SetAllowExternalContributionsParameters) =
postServer<SetAllowExternalContributionsParameters, String> (
parameters |> ensureCorrelationIdIsSet,
$"repository/{nameof (Repository.SetAllowExternalContributions)}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Publish the new repository setting route in OpenAPI

SetAllowExternalContributions adds a public SDK call and server route, but rg for setAllowExternalContributions/SetAllowExternalContributions found no path or parameter schema in src/OpenAPI/Grace.OpenAPI*.yaml or generated SDKs, only RouteClassification. Non-.NET generated clients therefore cannot call the new setting even though the server exposes it.

Useful? React with 👍 / 👎.

Comment on lines +1979 to +1980
| UploadSessionCommand.Abandon operationId
| UploadSessionCommand.Expire operationId ->

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Release unattached finalized upload reservations

Reservations are released only for Abandon and Expire, but a successfully finalized upload that is never attached moves to RetentionPending and is later cleaned up through DeletePhysicalState; that path compacts/deactivates without releasing an unsettled reservation. In the common failed-or-never-retried save case after a manifest finalize, UsedBytes/ExternalBytes stay consumed forever even though the upload coordination state has been deleted.

Useful? React with 👍 / 👎.

@ScottArbeit

Copy link
Copy Markdown
Owner Author

Replacement-Model Stabilization Stop: Retained Storage Must Be Server-Authoritative

Codex completed review 4677429660 on current head 0b965fa2a841ef8654188c005df4db6c97f50a56 with nine fresh findings.
Current-head CI run 29148036443 also completed. Coding is stopped before another worker because the review exposes a
new durable retention/materialization concept not yet approved in #653.

Review And CI Audit

Superseded findings:

  • All earlier Codex cycles belong to the removed ContentOwnershipLedger payer-transfer model. They remain historical
    evidence but are not routed as fixes on the replacement head.

Fresh routine findings within the approved replacement contract:

  • 3563889843: require repository write/admin permission for changing AllowExternalContributions.
  • 3563889856: publish the new setting route and parameters in OpenAPI/generated clients.
  • CI: make pure Server.Unit initialization independent of a real Azure storage account key.
  • CI: enable AllowsLargeFiles explicitly in the existing large-binary lifecycle test.
  • CI: correct reservation operation identity for repeated upload-credential requests.

Fresh structural findings:

  • 3563889844: whole-file and fully deduplicated upload paths can retain bytes without the content-block reservation
    boundary.
  • 3563889847: a reference can become durable before settlement rejects, leaving retained but unaccounted content.
  • 3563889849: settled usage is not released when references stop retaining it.
  • 3563889851: promotion never invokes BillingAccount reclassification.
  • 3563889852: enforcing branch count before the concrete branch is known blocks updates to already-counted branches.
  • 3563889854: optional client UploadSessionIds can be omitted to bypass accounting.
  • 3563889857: finalized unattached reservations can survive physical-state deletion without release.

Current-head CI separately proves that the old /repository/create timeout is fixed: shared setup successfully created
the owner, organization, and three repositories, then Grace.Server.Tests ran 259 tests with 257 passing.

Missing Invariant Family

UploadSessionIds in event Properties is valid provenance, but optional client metadata cannot be the billing or abuse
prevention source of truth. Hard cutoff and branch-count enforcement require a server-owned decision before a retaining
event becomes durable. Logical deletion and promotion also require server-owned retained-usage lifecycle state.

Proposed invariant family:

  • Server-computed Merkle old-root/new-root delta is the source of logical retained bytes.
  • A durable idempotent RetentionClaim atomically claims the byte delta and any newly active external branch slot before
    the retaining event.
  • Successful event creation finalizes the claim; failed or expired claims release it; same-command replay repairs the
    same claim.
  • Upload reservations remain branch-independent until the concrete retaining command. UploadSessionIds validates and
    matches provenance but omission cannot change the server-computed usage decision.
  • Existing active external branches consume no new branch slot when updated at the limit.
  • Unretaining root deltas release logical usage; promotion removes external classification without changing total bytes.

This is a new durable pending-state and materialization-domain decision. It is not safe to infer from the existing
client-retry-only settlement rule.

Decision Page

The local unblock page explains four choices with practical tradeoffs:

C:\Users\scott\AppData\Local\Temp\grace-638-orchestration\grace-653-unblock-current.html

No fix worker is assigned. After maintainer approval, revise the #653 issue body and #638 stabilization ledger, then
assign one fresh GPT-5.6-Sol Medium structural stabilization worker that owns both the approved structural model and the
classified routine CI/review fixes.

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