Skip to content

feat(authz): recover external owner writes from durable intents - #2187

Open
AlexStocks wants to merge 5 commits into
oceanbase:masterfrom
AlexStocks:codex/2084-external-owner-recovery
Open

AlexStocks wants to merge 5 commits into
oceanbase:masterfrom
AlexStocks:codex/2084-external-owner-recovery

Conversation

@AlexStocks

@AlexStocks AlexStocks commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Which issue or RFC does this PR close?

Closes #2159. Refs #2084 (R4), RFC 1396 and merged #2164.

Rationale for this change

When ownership and audit live outside the business transaction, a committed Artifact or Candidate can outlive a failed owner write. Retrying as the current caller must not replace the original initiating identity. Historical catalogs may also have no trusted ownership evidence at all.

What changes are included in this PR?

  • Commit immutable ownership intents in pc_access_owner_recovery alongside supported domain writes. Retain only trusted Principal identity, exact resource/Candidate/target, original idempotency key and minimized audit context.
  • Deliver after the transaction and before a successful response; keep failures durable and retry bounded pages in the Server lifecycle. Decode each intent separately so a damaged row does not block the rest of its page. Conflicting owner/key/target values remain conflicts. Validate persisted scope and operation identity before replay.
  • Add optional IdempotentAccessAuditStore.append_audit_once and its canonical relational implementation. Stable recovery event IDs prevent duplicate audit events after lost acknowledgements. A missing external audit capability rolls the domain write back before acceptance.
  • Cover base creation, Candidate proposal/generation/upload/import, publication, Handoff, scheduled incubation and Profile commit hooks. Idempotent publication/Handoff retries and HTTP/scheduled Profile flush retries replay the original persisted intent before success, even when domain processing bypasses its commit hook. Same-database relationship/audit transactions keep their existing atomic behavior.
  • Provide trusted embedded operators with pending, metadata-only paginated inventory_gaps, and replay(operation_id). Unknown historical owners are reported without reading protected bodies or assigning the current user. Completed receipts are retained.
  • Document the operator procedure, compatibility boundaries and independent read-grant semantics in both locales. Candidate approval retains its existing durable proposed-owner/terminal-result retry path; custom assemblies without commit hooks retain their explicit business-retry guarantees.

Are there any user-facing changes?

External-store creation paths can now recover after restart using the original trusted identity. Delivery failure still returns 503; a non-idempotent create must not be repeated blindly. External Artifact recovery requires event-idempotent audit. No owner-assignment endpoint, HTTP/MCP recovery API, CLI, ownership transfer or automatic historical backfill is added.

An independent read grant remains valid for an ownerless identity; reading never establishes ownership. Legacy Memory collection writes remain unsupported. Atomic Memory, Dream and supervised-worker security keep their existing domain transaction requirements.

How was this change tested?

Current source validation for aa2919265028eea1eef758ec3ce2b1b0d4d516e2, including master 6b976859195b6310960f7d7f9ea5bce423ebaaa8:

  • Linux/Python 3.11: pytest tests/e2e/test_scheduled_experience_incubation.py tests/test_owner_recovery.py tests/e2e/test_owner_recovery_http.py tests/e2e/test_access_control_regressions.py tests/e2e/test_profile_api.py tests/test_access_http.py tests/test_server.py -q --tb=short: 173 passed, 2 skipped. The two skips are the opt-in seekDB/OceanBase batch-incubation cases; the SQLite case and scheduled processing test passed.
  • Two batch-recovery regressions fail without batch staging and pass with it. Separate real SQLite stores establish commit/rollback coupling and reopening/replay with each original proposer, target and idempotency key preserved.
  • Repository hooks passed with SKIP=ty-check prek run -a; Ty was run separately with the Linux environment: ty check --python /tmp/pc2167-venv --python-version 3.11 --extra-search-path tests/e2e/cross_host_work_continuity/fixtures passed. The explicit search path resolves the existing standalone acceptance script's from task import increment; no repository type-check configuration changed. The default WSL invocation cannot resolve the Windows virtualenv. Plain full Linux Ty without that fixture search path reports the pre-existing standalone import; a default make check pass is not claimed.
  • Recovery tests use two independent real SQLite databases with injected external-delivery and primary-acknowledgement failures. They cover rollback, reopening, Server background recovery, concurrent recovery and normal retry, stable audit identity, owner/target conflicts, invalid persisted metadata, unknown-history inventory and missing audit capability. HTTP publication, Handoff and Profile retry regressions preserve the accepted request context.
  • git diff --check passed. The only manual merge conflict was the scheduled Candidate owner hook: same-database batch attestation is retained, while external batches stage durable intents before domain commit.

Limits: these results establish separate SQLite store behavior and application lifecycle recovery. Remote relationship-service, OceanBase/MySQL and multi-Server ownership recovery acceptance were not executed. Existing custom assemblies without domain commit hooks do not gain this guarantee. New-Head GitHub CI and independent review remain required.

AI usage statement

Implemented and verified with OpenAI Codex. AI-generated changes were checked against the current contracts, existing Access regressions and real SQLite failure-injection tests.

Persist trusted owner and candidate attestation intents in the business
transaction, then replay them outside it before returning success. Retain
stable audit identity across retries, process restarts and lost acknowledgements.
Expose bounded, content-free operator inventory without inferring historical owners.

Constraint: Preserve immutable owners, exact candidate targets and independent read grants; retain same-database atomic writes and domain-specific security requirements.
Scope-risk: Adds a Server-owned recovery table for external-store compositions. Artifact recovery requires an event-idempotent audit store; custom assemblies without commit hooks retain their existing retry guarantees.
Tested: Windows ownership/Access regressions 129 passed; Linux recovery/document tests 27 passed; changed-file Ruff/format/pre-commit checks and full Linux Ty/Python 3.11 passed. SQLite first-SAVEPOINT rollback regression fails without the transaction guard.
Copilot AI balanced review requested due to automatic review settings October 22, 2026 05:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

Read pending operation IDs before decoding each intent so malformed metadata
cannot starve unrelated recovery work. Preserve fail-closed explicit replay.
Exercise provider disappearance with the real built-in Access service rather
than an incomplete startup-only test double.

Constraint: Keep original ownership and all provider-disappearance denial assertions; never repair a damaged intent by guessing an identity.
Scope-risk: Changes only recovery polling and the startup test fixture.
Tested: Damaged-page regression fails before the fix; focused recovery 19 passed, Windows ownership/Access regressions 130 passed, Server suite 58 passed.
Copilot AI balanced review requested due to automatic review settings October 22, 2026 05:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@hidb4ai hidb4ai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two issues remain: an idempotent publication retry can succeed before its required audit is delivered, and the new background-recovery test can cancel delivery before asserting the audit.

),
**({"on_commit": on_commit} if access is not None else {}),
)
await flush_owner_writes()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Finish the original recovery intent on an idempotent retry

If append_audit_once fails after the external owner commits, the first publication request returns 503. Retrying the same payload/key returns the existing publication without executing on_commit, so this session has no tracked operation IDs and flush_owner_writes() does nothing. The subsequent owner-only check passes. With two real SQLite stores, I reproduced 503 followed by 201 while the audit remained unavailable, the intent remained pending, and no artifact-owner-established audit existed. Before returning success, retries must complete the original accepted intent or retain the failure; preserve its original identity and audit context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8ccd0c7. Idempotent publication retries replay the original persisted Artifact intent before returning success, including its original audit event. The same gap existed for Handoff commit retries and is fixed there as well. No ownership is restaged from the retrying Principal or request context.

Both HTTP regressions reproduce the original success response while audit remains unavailable. They now retain 503 until the audit recovers, then complete one original audit event, clear the intent and return the same domain result on repeat. The checks use two independent real SQLite stores and verify original request correlation and no duplicate Artifact/audit.

Comment on lines +348 to +350
async with recovery.monitor(), asyncio.timeout(12):
while await access.artifact_owner(other) is None: # noqa: ASYNC110 - observe persisted external state
await asyncio.sleep(0.05)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Wait for delivery completion before stopping the monitor

The owner relation becomes visible before _deliver_artifact finishes writing its audit. Once this polling loop observes the relation, exiting recovery.monitor() cancels the worker. Cancellation between those writes leaves zero audit events, so the assertion below fails even though recovery correctly retains the pending intent. I reproduced this failure in the unmodified test; a subsequent run passed. Keep the monitor running until the healthy intent's delivery/audit is complete before asserting the result and cancelling the worker.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8ccd0c7. The monitor stays active until the healthy Scope has no pending delivery intents, so the audit and primary acknowledgement finish before cancellation. The deliberately corrupted row remains in its separate damaged Scope and is still explicitly rejected. All original owner and audit assertions are preserved.

Replay the persisted Artifact intent when publication or Handoff returns an idempotent result without invoking its commit hook. Keep the original Principal and audit context and retain failure until delivery finishes. Wait for durable recovery completion before cancelling the monitor test.

Constraint: Never create or restage ownership from the retrying identity.
Scope-risk: HTTP publication/Handoff retries with external ownership and audit stores.
Tested: Both new HTTP cases fail before the fix; Windows recovery/HTTP 21 passed; Linux recovery/HTTP/access regressions 86 passed; changed-file hooks and full Linux Ty/Python 3.11 passed.
Copilot AI balanced review requested due to automatic review settings October 22, 2026 10:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

async def flush_owner_writes(*, artifact: ResourceRef | None = None) -> None:
session = _SESSION.get()
if session is not None:
while session.operation_ids:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Replay pending ownership intents on Profile flush retries

After a Profile flush commits but external delivery fails, retrying can return review_pending or noop without invoking the commit hook, leaving this session's operation IDs empty and returning success while ownership delivery remains incomplete. Replay the original persisted Profile or Candidate intent before allowing these retry responses to succeed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in e8c79f0. HTTP and scheduled Profile flush responses now replay the original persisted Profile intent and the returned Candidate intent before returning success, including noop/review_pending retries that bypass the commit hook. No owner or audit context is restaged from the retry identity.

Real HTTP regressions with two independent SQLite stores reproduce the previous 200 response while relationship or audit delivery remains unavailable. Retries now retain 503 and the same pending intent until delivery recovers, then preserve the original owner/request correlation and return the existing Profile or Candidate without duplicate domain writes.

Complete the original persisted Profile or Candidate ownership intent before
HTTP and scheduled flush retries return noop or review_pending. Keep the
accepted identity and audit context when external delivery is still failing.

Constraint: replay only existing durable intents; never stage ownership from
retry identity or infer ownership for historical records.
Scope-risk: external ownership delivery on Profile flush response paths.
Tested: focused recovery, authorization and Profile HTTP suites (95 passed);
HTTP/background snapshot authorization (2 passed); full Python 3.11 ty check;
scoped prek hooks. The three new HTTP cases failed before the source fix.

Refs: oceanbase#2084, oceanbase#2187
Copilot AI balanced review requested due to automatic review settings October 22, 2026 21:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@AlexStocks AlexStocks added the 1.4.1 v1.4.1 release tracking label Oct 24, 2026
@hidb4ai hidb4ai added 1.5.0 v1.5.0 release tracking and removed 1.4.1 v1.4.1 release tracking labels Oct 25, 2026
Merge current master into external owner recovery. Keep relational batch attestation and stage external batch intents in the original domain transaction. Remove the unused cast import exposed by the automatic merge.

Constraint: Preserve original proposer, exact target, rollback and replay semantics.
Scope-risk: The scheduled Candidate ownership hook now supports both batch attestation and durable external delivery; no changes to another author's branch.
Tested: Focused recovery, HTTP Access, Profile, scheduler and Server tests (173 passed, 2 disabled native backend cases skipped); batch regressions (2 failures before implementing batch staging); prek run -a with ty-check run separately; Linux Ty/Python 3.11 with the standalone acceptance-fixture search path (all checks passed); git diff --check.
Copilot AI balanced review requested due to automatic review settings October 25, 2026 07:55
@AlexStocks

Copy link
Copy Markdown
Contributor Author

Resolved the master conflict in aa2919265028eea1eef758ec3ce2b1b0d4d516e2 by merging 6b976859195b6310960f7d7f9ea5bce423ebaaa8.

The scheduled Candidate hook retains master's batched relational attestation and this PR's durable external ownership intents. Batch commit/rollback and reopen/replay regressions preserve the original proposers and exact targets. Focused checks: 173 passed, 2 disabled native backend cases skipped. Lint/format hooks and full Linux Ty with the documented fixture search path passed; the PR body records the environment limits and exact command. New-Head CI and reviewer confirmation remain pending.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.5.0 v1.5.0 release tracking

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(authz): define durable external owner recovery and historical reconciliation

4 participants