Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
015443b
fix(mcp): clear transitive dependency audit advisories
rhein1 Sep 30, 2026
59bc5d9
fix(ci): refresh n8n and Claude SDK vulnerable transitive locks
rhein1 Sep 30, 2026
fba3992
fix(mcp): clear transitive dependency audit advisories
rhein1 Sep 30, 2026
164c2da
fix(ci): refresh n8n and Claude SDK vulnerable transitive locks
rhein1 Sep 30, 2026
1506500
fix(governance): preserve action receipts across lifecycle failures
rhein1 Sep 30, 2026
5ff6db1
fix(governance): preserve cancellation when receipt persistence fails
rhein1 Sep 30, 2026
e1145d8
fix(governance): avoid invoking error code accessors
rhein1 Sep 30, 2026
1cb4fb4
fix(governance): retain nested receipts on cancellation write failure
rhein1 Sep 30, 2026
1d95644
fix(n8n): qualify audit-clean development workflow fixture
rhein1 Sep 30, 2026
a26fcfb
fix(n8n): qualify audit-clean development workflow fixture
rhein1 Oct 1, 2026
c0679c5
fix(n8n): avoid Windows audit subprocess path parsing
rhein1 Oct 1, 2026
a95729e
chore: renew bounded MatrAIx inventory review
rhein1 Oct 1, 2026
ca1ee6a
Merge shared integration validation repair into PR 449
rhein1 Oct 1, 2026
a1283ee
fix(deps): align hosted MCP bundle with reviewed dependency closure
rhein1 Oct 2, 2026
c83146c
Merge reviewed hosted MCP dependency closure repair
rhein1 Oct 2, 2026
5f5758a
fix(governance): preserve nested receipt and exception identities
rhein1 Oct 2, 2026
f65143f
ci: run governed receipt identity regressions
rhein1 Oct 2, 2026
22c2ee3
merge: integrate reviewed governance receipts with current main
rhein1 Oct 4, 2026
8249686
Merge branch 'main' into codex/govern-receipt-lifecycle-334
rhein1 Oct 14, 2026
42038f4
Merge branch 'main' into codex/govern-receipt-lifecycle-334
rhein1 Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ permissions:
contents: read

jobs:
python-sdk-compat:
runs-on: ubuntu-22.04
strategy:
matrix:
python: ['3.8', '3.14']
steps:
- uses: actions/checkout@v7.0.1
- uses: actions/setup-python@v7.0.0
with:
python-version: ${{ matrix.python }}
- name: Test dependency-free Python SDK at minimum and current versions
env:
PYTHONPATH: sdk/python/src
run: python -m unittest discover -s sdk/python/tests -p 'test_*.py'

validate:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -134,6 +149,8 @@ jobs:
run: |
node --test test/sdk-public-source.test.mjs
node --test test/local-governance-cli.test.mjs
node --test test/governance-lifecycle.test.mjs
node --test test/governance-receipt-identity.test.mjs
node --check sdk/node/index.js
node --check sdk/node/index.mjs
node --check sdk/agent-os-cli/cli.js
Expand Down
20 changes: 20 additions & 0 deletions sdk/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,26 @@ const safeTool = govern(sendEmail, {

The approval callback receives only the normalized action and argument count. Result evidence is shape-only by default, and raw tool inputs or outputs are not persisted.

Callback failures are recorded by phase. Approval failure produces `approval_failed`
without dispatch; evidence failure after a successful tool produces
`completed_evidence_failed`. A failed `onReceipt` raises `receipt_callback_failed`
with the original receipt in both `error.response.receipt` and
`error.agoragenticReceipt`, without writing a second action receipt. Approval, tool,
and evidence exceptions retain their identity and use the `agoragenticReceipt`
access convention when the thrown value can accept properties. `receipts: false`
disables persistence and supplies `null` to receipt callbacks.
Receipt callbacks receive a deep copy, so their mutations cannot change the
canonical receipt retained on notification errors. Nested failures retain the
inner `agoragenticReceipt`; successfully persisted enclosing action receipts are
listed separately in `agoragenticEnclosingReceipts`, from inner to outer.
If recording an approval, tool, or evidence failure itself fails, the original
exception still propagates. Where metadata attachment is possible,
`agoragenticReceiptError` records only `code: "receipt_persistence_failed"`,
`action`, `phase`, and `outcome`; it does not claim a durable receipt or include
storage paths or error messages. Frozen, primitive, or hostile thrown values may
not expose metadata; any successfully persisted receipts remain on disk.
These wrappers do not retry, resume, roll back effects, or provide restart-safe execution.

## Hosted Router Model

`agoragentic` is a thin client to the Agoragentic-hosted Agent OS router.
Expand Down
101 changes: 71 additions & 30 deletions sdk/node/local-governance.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,31 @@ function govern(tool, options = {}) {
prepareReceiptDirectory(policy, cwd);
let approved = options.approved === true;
const initial = evaluatePolicy(policy, action, { approved });
const startedAt = nowIso(options);
const record = (decision, outcome, evidence) => writeReceipt(policy, cwd, buildReceipt({
options, action, classification: 'local_tool_evidence', decision,
startedAt, finishedAt: nowIso(options), outcome,
evidence: { argument_count: args.length, ...evidence },
}));
const recordFailure = (error, decision, phase, outcome) => {
try {
attachReceipt(error, record(decision, outcome, { error_code: safeErrorCode(error) }));
} catch {
// The original failure takes precedence; do not claim a durable receipt.
attachMetadata(error, 'agoragenticReceiptError', {
code: 'receipt_persistence_failed', phase, action, outcome,
});
}
};
if (initial.decision === 'ask' && !approved && typeof options.approve === 'function') {
approved = await options.approve({ action, argument_count: args.length }) === true;
try {
approved = await options.approve({ action, argument_count: args.length }) === true;
} catch (err) {
recordFailure(err, initial, 'approval', 'approval_failed');
throw err;
}
}
const decision = evaluatePolicy(policy, action, { approved });
const startedAt = nowIso(options);
if (!decision.execute) {
const receipt = writeReceipt(policy, cwd, buildReceipt({
options,
Expand All @@ -248,42 +268,56 @@ function govern(tool, options = {}) {
throw governanceError(decision.reason, `Action ${action} was not executed: ${decision.reason}.`, 3, { receipt });
}

let result;
try {
result = await tool(...args);
} catch (err) {
recordFailure(err, decision, 'tool', 'failed');
throw err;
}
let evidence;
try {
const result = await tool(...args);
const finishedAt = nowIso(options);
const evidence = typeof options.evidence === 'function'
evidence = typeof options.evidence === 'function'
? summarizeEvidence(await options.evidence(result))
: summarizeEvidence(result);
const receipt = writeReceipt(policy, cwd, buildReceipt({
options,
action,
classification: 'local_tool_evidence',
decision,
startedAt,
finishedAt,
outcome: 'completed',
evidence: { argument_count: args.length, result: evidence },
}));
if (typeof options.onReceipt === 'function') await options.onReceipt(receipt);
return result;
} catch (err) {
const finishedAt = nowIso(options);
const receipt = writeReceipt(policy, cwd, buildReceipt({
options,
action,
classification: 'local_tool_evidence',
decision,
startedAt,
finishedAt,
outcome: 'failed',
evidence: { argument_count: args.length, error_code: safeErrorCode(err) },
}));
err.agoragenticReceipt = receipt;
recordFailure(err, decision, 'evidence', 'completed_evidence_failed');
throw err;
}
const receipt = record(decision, 'completed', { result: evidence });
if (typeof options.onReceipt === 'function') {
try {
// Callback-owned copy: retained evidence must match the persisted JSON.
await options.onReceipt(receipt === null ? null : JSON.parse(JSON.stringify(receipt)));
} catch (err) {
const error = governanceError('receipt_callback_failed', 'Receipt callback failed after the tool completed.', 1, { receipt });
error.agoragenticReceipt = receipt;
error.cause = err;
throw error;
}
}
return result;
};
}

function attachReceipt(error, receipt) {
try {
const existing = Object.getOwnPropertyDescriptor(error, 'agoragenticReceipt');
if (!existing || existing.value == null) {
attachMetadata(error, 'agoragenticReceipt', receipt);
} else if (receipt !== null) {
const enclosing = Object.getOwnPropertyDescriptor(error, 'agoragenticEnclosingReceipts')?.value;
attachMetadata(error, 'agoragenticEnclosingReceipts', [...(Array.isArray(enclosing) ? enclosing : []), receipt]);
}
} catch { /* hostile/primitive/frozen errors retain identity; receipts remain on disk */ }
}

function attachMetadata(error, key, value) {
try {
Object.defineProperty(error, key, { value, writable: true, configurable: true, enumerable: true });
} catch { /* diagnostic metadata must never replace the original failure */ }
}

async function runGovernedCommand(executable, args = [], options = {}) {
if (!executable || typeof executable !== 'string') {
throw governanceError('command_missing', 'A command is required after "agoragentic run --".', 2);
Expand Down Expand Up @@ -451,7 +485,14 @@ function nowIso(options) {
}

function safeErrorCode(err) {
return typeof err?.code === 'string' && /^[A-Za-z0-9_.-]{1,64}$/.test(err.code) ? err.code : 'error';
try {
// Inspect only the own data property: error accessors are untrusted
// executable code, and even descriptor inspection can throw on a Proxy.
const descriptor = Object.getOwnPropertyDescriptor(err, 'code');
const code = descriptor?.value;
if (typeof code === 'string' && /^[A-Za-z0-9_.-]{1,64}$/.test(code)) return code;
} catch { /* primitive/revoked/hostile values retain a generic bounded code */ }
return 'error';
}

function governanceError(code, message, exitCode = 1, response) {
Expand Down
32 changes: 32 additions & 0 deletions sdk/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,38 @@ result = safe_tool(recipient="owner@example.com", body="review requested")

The approval callback receives only the normalized action, argument count, and keyword names. Local receipts persist result shape rather than raw inputs or outputs, keep spend and retry authority owner-only, and cannot escape the project through configured paths. A receipt proves only the wrapped local call. It is not host, provider, deployment, payment, settlement, or on-chain proof.

Async cancellation propagates `asyncio.CancelledError` with the phase receipt in
`error.agoragentic_receipt`. Cancellation during approval records `approval_failed`
without dispatch; during the tool it records `cancelled_effect_uncertain`, which
does not mean rollback or safe retry. After tool success, cancellation during
evidence capture records `completed_evidence_failed`. Cancellation during receipt
notification retains the original completed receipt and its ID. Ordinary callback
failures retain the existing `GovernanceError.receipt` convention. Disabling
receipts supplies `None` and performs no receipt writes. Neither wrapper retries,
resumes, rolls back effects, or provides restart-safe execution.

If receipt persistence fails while handling cancellation, the original
`CancelledError` still propagates and the task remains cancelled.
`agoragentic_receipt` is `None` unless a nested governed call already attached
its own durable receipt, which is retained on outer persistence failure.
It is also retained when outer persistence succeeds: enclosing action receipts
are listed separately in `agoragentic_enclosing_receipts`, from inner to outer.
Nested ordinary failures preserve `GovernanceError.receipt` and its
`agoragentic_receipt` alias in the same way. Receipt callbacks receive deep copies,
so callback mutation cannot change the canonical receipt retained on errors.
`agoragentic_receipt_error` contains only `code: "receipt_persistence_failed"`,
the failed action identity, the phase, and the attempted outcome. An inner
action's retained receipt does not prove persistence for the failed outer action.
That marker is not a durable receipt or proof that a partial file was removed;
it contains no filesystem path or persistence-exception message.
The same bounded marker accompanies ordinary failure-recording errors while
preserving tool exceptions and existing callback `GovernanceError` cause chains.
All exception metadata is best-effort: a subclass that rejects attribute writes
still propagates unchanged, and successfully persisted receipts remain on disk.
These identity guarantees apply at the governed coroutine boundary. Python 3.8
may replace `CancelledError` when it crosses a cancelled `Task` boundary; catch
inside that task to inspect metadata, or reconcile the persisted receipts.

## Hosted Router Model

`agoragentic` is a thin client to the Agoragentic-hosted Agent OS router.
Expand Down
Loading
Loading