Skip to content

Commit 160f856

Browse files
committed
Clarify verify and capability warning semantics
1 parent e8a5808 commit 160f856

4 files changed

Lines changed: 87 additions & 0 deletions

File tree

docs/architecture.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Each provider file auto-registers with its registry on import (side-effect regis
6868
- **Phase 4.5: Authorization** (optional) -- invoke external policy engine (OPA, Cedar, Topaz) when an authorization block is configured. Decisions: `permit`, `deny`, `require-escalation`. Skipped entirely when no authorization block resolves for the task.
6969
- **Phase 5: Execution** -- run the tool, capture stdout/stderr/exit code/duration, compute hashes.
7070
- **Phase 6: Evidence Generation** -- build canonical evidence payload, attest execution, verify evidence if required.
71+
- **Phase 6.5: Post-exec Verify** (optional) -- run `workflow.verify` / `task.verify` after a successful command. This is an operator-local postcondition check recorded separately from evidence; verify failures can still fail the task or downgrade to warnings according to `verify.on_failure`.
7172
- **Phase 7: Audit** -- write structured append-only audit record with declared/resolved identity, authorization proof summary, delegation chain, trust level, authorization decision, and runtime instance attribution.
7273
- **Phase 8: Cleanup** -- delete temporary files, destroy ephemeral materialization and derived handoff credentials.
7374

docs/execution-identity.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,14 @@ Evidence verification occurs after execution (Phase 5) has already completed. A
17221722
- when `verify.required` is `false`, a verification failure is recorded as a warning but does not affect the exit code
17231723
- the evidence envelope (including the failed verification status) is always written to the audit record so that operators can investigate
17241724

1725+
### Phase 6.5: Post-execution Verify
1726+
1727+
- only enter this phase when the main command exited successfully and a workflow/task `verify` block resolves
1728+
- run the declared verify shell in the task's effective execution context
1729+
- treat `verify` as an operator-local postcondition separate from evidence attestation; the attested evidence payload reflects the main command result, not the later verify shell outcome
1730+
- when `verify.on_failure` is `error`, return a non-zero status after cleanup and audit
1731+
- when `verify.on_failure` is `warn`, record the verify failure as a warning without changing the exit code
1732+
17251733
### Phase 7: Audit
17261734

17271735
- write append-only audit record

docs/field-reference.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,16 @@ When `ref` is present, the referenced profile is loaded first, then inline field
274274
|-------|------|----------|--------|-------------|
275275
| `child_credential_policy` | string | No | `none`, `inherit`, `downscope`, `independent` | Controls how a child task receives or derives credentials relative to its parent. Workflow-level values act as defaults for tasks. |
276276

277+
`child_credential_policy: "downscope"` is validated as a capability warning when a backend lacks `credential_handoff`: the scheduler can still persist the job, but child narrowing will not be enforceable at dispatch. This is intentionally softer than `identity.presentation.handoff != "none"`, which is a hard compatibility requirement because the active runtime/backend must advertise explicit handoff semantics up front.
278+
277279
---
278280

279281
## Task Verify Fields
280282

281283
Runs a shell command after the main task succeeds. Workflow-level `verify` acts as the default for tasks; a task-level `verify` replaces the workflow block and omitted optional fields fall back to built-in defaults.
282284

285+
In the v0.2 execution pipeline, `verify` runs after evidence generation. Evidence and attestation therefore describe the main command result; the `verify` outcome is recorded separately and can still flip the final task status according to `on_failure`. If operators need end-to-end proof that includes the verification step, model that requirement in the evidence payload rather than assuming `verify` is part of the attested result.
286+
283287
| Field | Type | Required | Values | Description |
284288
|-------|------|----------|--------|-------------|
285289
| `shell` | string | Yes | -- | Shell command to run after a successful task execution. |
@@ -346,6 +350,8 @@ Runs a shell command after the main task succeeds. Workflow-level `verify` acts
346350
| `cleanup` | string | No | `always`, `on-success`, `on-failure`, `never` | When credential cleanup runs. |
347351
| `default_redaction` | boolean | No | -- | Whether credential values are redacted by default in audit output. |
348352

353+
`identity.presentation.handoff` is stricter than `child_credential_policy`: any non-`none` handoff mode requires explicit `credential_handoff` support from the active runtime/backend during capability negotiation, because the handoff boundary itself must be modeled first-class.
354+
349355
### Identity Presentation Bindings
350356

351357
Each element in the `bindings` array is an object with these fields:

test/agentcli.test.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7577,6 +7577,78 @@ test('applyManifestToScheduler includes capabilities metadata in result', async
75777577
assert.strictEqual(result.capabilities.handoff_version, '2');
75787578
});
75797579

7580+
test('applyManifestToScheduler preserves capability warnings in structured result', async () => {
7581+
const manifest = {
7582+
version: '0.2',
7583+
workflows: [{
7584+
id: 'warning-wf',
7585+
name: 'Warning Workflow',
7586+
tasks: [{
7587+
id: 'child-task',
7588+
name: 'Downscope Child',
7589+
prompt: 'exercise capability warning path',
7590+
target: { session_target: 'isolated' },
7591+
schedule: { cron: '0 * * * *' },
7592+
delivery: { mode: 'none' },
7593+
child_credential_policy: 'downscope',
7594+
}]
7595+
}]
7596+
};
7597+
const runner = {
7598+
invocation: { label: 'fake-scheduler' },
7599+
queryCapabilities() {
7600+
return {
7601+
scheduler_version: '0.2.0',
7602+
schema_version: 22,
7603+
handoff_version: '2',
7604+
features: {
7605+
approvals: 'runtime',
7606+
runtime_execution: true,
7607+
identity_declaration: true,
7608+
runtime_identity_resolution: true,
7609+
trust_evaluation: true,
7610+
authorization_proof_verification: true,
7611+
authorization_hook: true,
7612+
evidence_generation: true,
7613+
delegation_validation: false,
7614+
credential_handoff: false,
7615+
audit_export: true
7616+
}
7617+
};
7618+
},
7619+
listJobs() {
7620+
return [];
7621+
},
7622+
addJob(spec) {
7623+
return { ok: true, job: spec };
7624+
},
7625+
updateJob(id, spec) {
7626+
return { ok: true, job: spec };
7627+
}
7628+
};
7629+
7630+
const stderrWrites = [];
7631+
const originalStderrWrite = process.stderr.write;
7632+
process.stderr.write = (chunk, ...args) => {
7633+
stderrWrites.push(String(chunk));
7634+
const cb = args.find(arg => typeof arg === 'function');
7635+
if (cb) cb();
7636+
return true;
7637+
};
7638+
7639+
try {
7640+
const result = await applyManifestToScheduler(manifest, { runner });
7641+
assert.strictEqual(result.ok, true);
7642+
assert.ok(Array.isArray(result.capabilities?.warnings));
7643+
assert.strictEqual(result.capabilities.warnings.length, 1);
7644+
assert.strictEqual(result.capabilities.warnings[0].feature, 'credential_handoff');
7645+
assert.ok(result.capabilities.warnings[0].message.includes('child_credential_policy="downscope"'));
7646+
assert.ok(stderrWrites.some(line => line.includes('child_credential_policy="downscope"')));
7647+
} finally {
7648+
process.stderr.write = originalStderrWrite;
7649+
}
7650+
});
7651+
75807652
test('applyManifestToScheduler skips runtime capability queries for pure v0.1 manifests', async () => {
75817653
let capabilityCalls = 0;
75827654
const runner = {

0 commit comments

Comments
 (0)