approval-md 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -24
- package/SPEC.md +57 -11
- package/dist/src/channels/contract.d.ts +34 -1
- package/dist/src/channels/contract.js +200 -7
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/telegram.d.ts +123 -11
- package/dist/src/channels/telegram.js +218 -23
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/channels/web.d.ts +9 -0
- package/dist/src/channels/web.js +17 -0
- package/dist/src/channels/web.js.map +1 -1
- package/dist/src/cli/amend.js +214 -30
- package/dist/src/cli/amend.js.map +1 -1
- package/dist/src/cli/attest.d.ts +9 -0
- package/dist/src/cli/attest.js +134 -7
- package/dist/src/cli/attest.js.map +1 -1
- package/dist/src/cli/channel-telegram.d.ts +99 -26
- package/dist/src/cli/channel-telegram.js +311 -13
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/channel.d.ts +9 -0
- package/dist/src/cli/channel.js +9 -0
- package/dist/src/cli/channel.js.map +1 -1
- package/dist/src/cli/codex-bridge.d.ts +819 -0
- package/dist/src/cli/codex-bridge.js +1607 -0
- package/dist/src/cli/codex-bridge.js.map +1 -0
- package/dist/src/cli/codex.d.ts +1 -1
- package/dist/src/cli/codex.js +304 -7
- package/dist/src/cli/codex.js.map +1 -1
- package/dist/src/cli/daemon.js +4 -1
- package/dist/src/cli/daemon.js.map +1 -1
- package/dist/src/cli/doctor.js +467 -12
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/execute.js +25 -2
- package/dist/src/cli/execute.js.map +1 -1
- package/dist/src/cli/help.d.ts +6 -2
- package/dist/src/cli/help.js +165 -60
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook-codex.d.ts +49 -1
- package/dist/src/cli/hook-codex.js +60 -1
- package/dist/src/cli/hook-codex.js.map +1 -1
- package/dist/src/cli/hook.d.ts +459 -3
- package/dist/src/cli/hook.js +1062 -114
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/import.js +1 -1
- package/dist/src/cli/import.js.map +1 -1
- package/dist/src/cli/main.js +5 -3
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/policy-apply.d.ts +195 -0
- package/dist/src/cli/policy-apply.js +573 -0
- package/dist/src/cli/policy-apply.js.map +1 -0
- package/dist/src/cli/policy.js +14 -1
- package/dist/src/cli/policy.js.map +1 -1
- package/dist/src/cli/preflight.d.ts +151 -13
- package/dist/src/cli/preflight.js +398 -41
- package/dist/src/cli/preflight.js.map +1 -1
- package/dist/src/cli/sandbox.js +17 -1
- package/dist/src/cli/sandbox.js.map +1 -1
- package/dist/src/cli/scaffold.d.ts +1 -1
- package/dist/src/cli/scaffold.js +1 -1
- package/dist/src/cli/setup-channel.d.ts +9 -0
- package/dist/src/cli/setup-channel.js +28 -1
- package/dist/src/cli/setup-channel.js.map +1 -1
- package/dist/src/cli/setup-common.d.ts +3 -1
- package/dist/src/cli/setup-common.js +3 -2
- package/dist/src/cli/setup-common.js.map +1 -1
- package/dist/src/cli/setup.d.ts +2 -0
- package/dist/src/cli/setup.js +94 -2
- package/dist/src/cli/setup.js.map +1 -1
- package/dist/src/cli/up.js +115 -51
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/values.js +3 -4
- package/dist/src/cli/values.js.map +1 -1
- package/dist/src/cli/verb-registry.js +174 -9
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +2 -2
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/codex/broker.d.ts +229 -0
- package/dist/src/codex/broker.js +548 -0
- package/dist/src/codex/broker.js.map +1 -0
- package/dist/src/codex/runner.d.ts +178 -0
- package/dist/src/codex/runner.js +231 -0
- package/dist/src/codex/runner.js.map +1 -0
- package/dist/src/codex/serve.d.ts +56 -0
- package/dist/src/codex/serve.js +98 -0
- package/dist/src/codex/serve.js.map +1 -0
- package/dist/src/codex/workspace-commit.d.ts +219 -0
- package/dist/src/codex/workspace-commit.js +549 -0
- package/dist/src/codex/workspace-commit.js.map +1 -0
- package/dist/src/core/advance-cycle.d.ts +51 -0
- package/dist/src/core/advance-cycle.js +66 -2
- package/dist/src/core/advance-cycle.js.map +1 -1
- package/dist/src/core/agents-md.d.ts +20 -18
- package/dist/src/core/agents-md.js +33 -31
- package/dist/src/core/agents-md.js.map +1 -1
- package/dist/src/core/attest.d.ts +215 -0
- package/dist/src/core/attest.js +317 -7
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +18 -0
- package/dist/src/core/audit.js +13 -0
- package/dist/src/core/audit.js.map +1 -1
- package/dist/src/core/channel-owner.d.ts +213 -0
- package/dist/src/core/channel-owner.js +358 -0
- package/dist/src/core/channel-owner.js.map +1 -0
- package/dist/src/core/command-class.d.ts +154 -0
- package/dist/src/core/command-class.js +673 -20
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/commit-guard.d.ts +272 -0
- package/dist/src/core/commit-guard.js +424 -0
- package/dist/src/core/commit-guard.js.map +1 -0
- package/dist/src/core/daemon-actor.d.ts +45 -0
- package/dist/src/core/daemon-actor.js +54 -0
- package/dist/src/core/daemon-actor.js.map +1 -0
- package/dist/src/core/dark-session.d.ts +109 -8
- package/dist/src/core/dark-session.js +266 -82
- package/dist/src/core/dark-session.js.map +1 -1
- package/dist/src/core/decision-refusal.d.ts +23 -2
- package/dist/src/core/decision-refusal.js +24 -2
- package/dist/src/core/decision-refusal.js.map +1 -1
- package/dist/src/core/env-file.d.ts +5 -0
- package/dist/src/core/env-file.js +60 -1
- package/dist/src/core/env-file.js.map +1 -1
- package/dist/src/core/execute.d.ts +15 -2
- package/dist/src/core/execute.js +15 -2
- package/dist/src/core/execute.js.map +1 -1
- package/dist/src/core/gate.d.ts +86 -1
- package/dist/src/core/gate.js +81 -1
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/gesture-refusal.d.ts +166 -0
- package/dist/src/core/gesture-refusal.js +188 -0
- package/dist/src/core/gesture-refusal.js.map +1 -0
- package/dist/src/core/harness-version.d.ts +1 -1
- package/dist/src/core/harness-version.js +3 -1
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/instance.d.ts +59 -2
- package/dist/src/core/instance.js +113 -0
- package/dist/src/core/instance.js.map +1 -1
- package/dist/src/core/log.d.ts +39 -1
- package/dist/src/core/log.js.map +1 -1
- package/dist/src/core/policy-explain.d.ts +10 -0
- package/dist/src/core/policy-explain.js +32 -0
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +41 -1
- package/dist/src/core/policy-load.js +21 -3
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +43 -0
- package/dist/src/core/policy-match.js +52 -0
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +52 -0
- package/dist/src/core/policy-proposal.js +102 -2
- package/dist/src/core/policy-proposal.js.map +1 -1
- package/dist/src/core/protected-path-guard.d.ts +117 -4
- package/dist/src/core/protected-path-guard.js +362 -48
- package/dist/src/core/protected-path-guard.js.map +1 -1
- package/dist/src/core/question-preempted.d.ts +141 -0
- package/dist/src/core/question-preempted.js +152 -0
- package/dist/src/core/question-preempted.js.map +1 -0
- package/dist/src/core/read-scope.d.ts +172 -0
- package/dist/src/core/read-scope.js +252 -0
- package/dist/src/core/read-scope.js.map +1 -0
- package/dist/src/core/sandbox.d.ts +81 -0
- package/dist/src/core/sandbox.js +190 -1
- package/dist/src/core/sandbox.js.map +1 -1
- package/dist/src/core/sender-identity.d.ts +476 -0
- package/dist/src/core/sender-identity.js +572 -0
- package/dist/src/core/sender-identity.js.map +1 -0
- package/dist/src/core/shlex.d.ts +102 -0
- package/dist/src/core/shlex.js +159 -0
- package/dist/src/core/shlex.js.map +1 -0
- package/dist/src/core/values.d.ts +18 -8
- package/dist/src/core/values.js +36 -1
- package/dist/src/core/values.js.map +1 -1
- package/dist/src/daemon/advance.d.ts +10 -0
- package/dist/src/daemon/advance.js +25 -4
- package/dist/src/daemon/advance.js.map +1 -1
- package/dist/src/daemon/daemon.js +9 -0
- package/dist/src/daemon/daemon.js.map +1 -1
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/mcp/server.js +8 -0
- package/dist/src/mcp/server.js.map +1 -1
- package/docs/cli-reference.md +932 -32
- package/docs/codex-enforced-session.md +75 -2
- package/docs/codex-workspace-broker.md +118 -0
- package/package.json +3 -1
- package/schema/event.schema.json +538 -9
- package/schema/fixtures/event/invalid/approval-granted-sender-hashed-false.json +20 -0
- package/schema/fixtures/event/invalid/approval-granted-sender-hashed-raw-id.json +20 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-human-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-no-actor-no-sender.json +15 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-unknown-gesture.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-agent-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-no-question-id.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-unknown-source.json +15 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-absolute-path.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-agent-actor.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-missing-path.json +13 -0
- package/schema/fixtures/event/valid/approval-granted-sender-hashed.json +20 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-review-note.json +21 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-sender-key-unavailable.json +19 -0
- package/schema/fixtures/event/valid/audit-gesture-refused.json +19 -0
- package/schema/fixtures/event/valid/audit-question-preempted-no-verdict.json +16 -0
- package/schema/fixtures/event/valid/audit-question-preempted.json +20 -0
- package/schema/fixtures/event/valid/gate-path-signed-off.json +14 -0
- package/schema/fixtures/event/valid/harness-kind-claude-code.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-codex.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-cursor.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-grok.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-muse.json +23 -0
- package/schema/fixtures/policy/invalid/senders-half-keyed.json +20 -0
- package/schema/fixtures/policy/valid/canonical.json +1 -1
- package/schema/fixtures/policy/valid/senders-keyed.json +24 -0
- package/schema/fixtures/policy-md/valid/canonical.md +1 -1
- package/schema/fixtures/policy-md/valid/with-values.md +5 -7
- package/schema/fixtures/values/invalid/class-shaped.json +1 -1
- package/schema/fixtures/values/invalid/duplicate-entry.json +1 -1
- package/schema/fixtures/values/invalid/non-string-item.json +1 -1
- package/schema/fixtures/values/invalid/over-cap.json +1 -1
- package/schema/fixtures/values/invalid/unknown-key.json +1 -1
- package/schema/fixtures/values/invalid/version-float.json +1 -0
- package/schema/fixtures/values/invalid/version-integer.json +1 -0
- package/schema/fixtures/values/invalid/version-wrong-string.json +1 -0
- package/schema/fixtures/values/valid/empty-lists.json +2 -3
- package/schema/fixtures/values/valid/full.json +5 -7
- package/schema/fixtures/values/valid/minimal.json +1 -1
- package/schema/fixtures/values-md/invalid/schema-invalid.md +5 -3
- package/schema/fixtures/values-md/invalid/two-blocks.md +3 -3
- package/schema/fixtures/values-md/invalid/unterminated.md +2 -2
- package/schema/fixtures/values-md/invalid/version-1.md +69 -0
- package/schema/fixtures/values-md/invalid/version-unquoted.md +64 -0
- package/schema/fixtures/values-md/invalid/yaml-error.md +2 -2
- package/schema/fixtures/values-md/valid/absent.md +1 -1
- package/schema/fixtures/values-md/valid/with-values.md +5 -7
- package/schema/policy.schema.json +54 -2
- package/schema/values.schema.json +7 -11
- package/schema/fixtures/values/invalid/version-string.json +0 -1
|
@@ -26,5 +26,78 @@ both installed runtime versions unchecked.
|
|
|
26
26
|
The package does not run a postinstall script, call sudo, edit Codex
|
|
27
27
|
configuration, create accounts, load services, access credentials, start a
|
|
28
28
|
model, or alter APPROVAL.md. A human or MDM system must eventually install and
|
|
29
|
-
own the reviewed artifacts.
|
|
30
|
-
|
|
29
|
+
own the reviewed artifacts.
|
|
30
|
+
|
|
31
|
+
## The broker has landed; the runner has not (APRV-325.2)
|
|
32
|
+
|
|
33
|
+
`approval codex apply` and the one-tool server `approval codex serve` now exist:
|
|
34
|
+
a bounded create/replace/delete/move proposal against the manifest's workspace,
|
|
35
|
+
one registered action per distinct path class, authorized through the real gate,
|
|
36
|
+
every leg started before any byte moves, then staged, journaled and applied
|
|
37
|
+
under a workspace lock, with the outcome taken from reading the workspace back.
|
|
38
|
+
`docs/codex-workspace-broker.md` is its reference.
|
|
39
|
+
|
|
40
|
+
That is a gate on workspace writes and nothing more. On its own a door beside an
|
|
41
|
+
open window is decoration, which is what APRV-325.3 below closes.
|
|
42
|
+
|
|
43
|
+
## The confined session (APRV-325.3)
|
|
44
|
+
|
|
45
|
+
`approval codex start --manifest <abs>` prepares the room a session's shell runs
|
|
46
|
+
in, and `-- <command>` runs something inside it. Five things the shell does not
|
|
47
|
+
get, and where each is enforced:
|
|
48
|
+
|
|
49
|
+
| withheld | enforced by |
|
|
50
|
+
|---|---|
|
|
51
|
+
| canonical workspace writes | Seatbelt `(deny file-write*)` with an allow-list naming only the disposable workspace |
|
|
52
|
+
| gate writes (log, policy, vault, keys) | the same deny; the gate home is not on the allow-list |
|
|
53
|
+
| reads of anything but the two workspaces | Seatbelt `(deny file-read*)` with an allow-list of two roots (APRV-347's read jail) |
|
|
54
|
+
| ambient credentials | an environment ALLOW-list (`PATH`, `HOME`, `SHELL`, `USER`, `LANG`, `TERM` and a few more) applied after `core/child-env.ts`'s credential strip |
|
|
55
|
+
| credential material on disk | Seatbelt `denyRead` over the vault, the environment map and the sealing keys, emitted after the jail's allows so it is the last word |
|
|
56
|
+
| external egress | Seatbelt `(deny network-outbound)`, loopback included |
|
|
57
|
+
| mutable executor code | the broker, the CLI and the pinned Node live under the root-owned install root, which is on neither allow-list |
|
|
58
|
+
|
|
59
|
+
The read jail has exactly two roots, the disposable workspace and the canonical
|
|
60
|
+
workspace, and `core/sandbox.ts` adds the fixed runtime set and the running
|
|
61
|
+
command's own install prefix itself. A session has to READ the tree it is
|
|
62
|
+
reasoning about, which is why the canonical workspace is a root and why write
|
|
63
|
+
confinement rather than read denial is what stops it changing one. Everything
|
|
64
|
+
the operator's home holds beside it — other repositories, `~/.ssh`, the gate
|
|
65
|
+
home — is outside the jail and unreadable, and that is the half `denyRead` alone
|
|
66
|
+
could never cover: a deny-list has to have heard of a path to deny it.
|
|
67
|
+
|
|
68
|
+
Two properties are worth stating rather than implying. **There is no opt-out.**
|
|
69
|
+
`approval run` has `--no-sandbox`, because an operator holding a human's grant
|
|
70
|
+
may deliberately reach the world; a confined session has no such flag and no
|
|
71
|
+
unwrapped fallback, and a host with no sandbox mechanism REFUSES
|
|
72
|
+
(`sandbox-unsupported`) where `approval run` would record `unsupported` and
|
|
73
|
+
proceed. A session advertised as confined and not confined is worse than no
|
|
74
|
+
session. And **descendants are confined too**: Codex does not write files by
|
|
75
|
+
calling into this runtime, it spawns shells that do, so the proof that matters
|
|
76
|
+
is the one where the confined child's own `/bin/sh` grandchild is denied.
|
|
77
|
+
|
|
78
|
+
### Activation and rollback
|
|
79
|
+
|
|
80
|
+
The operator runbook lives in `docs/codex-activation.md`, under **Broker session
|
|
81
|
+
activation (Lane 4a)**: the ordered commands, which steps are human-only, and
|
|
82
|
+
how to roll back. That file also holds the native hook's trust ceremony, and the
|
|
83
|
+
two are deliberately separate claims — a green trust ceremony with no broker
|
|
84
|
+
enforces nothing, and a working broker with no Telegram proves nothing about
|
|
85
|
+
whether a decision can reach a human.
|
|
86
|
+
|
|
87
|
+
### What is still not proven
|
|
88
|
+
|
|
89
|
+
The confinement evidence covers processes this runtime spawns and their
|
|
90
|
+
descendants, on macOS. It is not a claim about a Codex desktop application a
|
|
91
|
+
person starts outside `codex start`: that process is not in the room, and what
|
|
92
|
+
was measured about it is in `docs/codex-boundary-probe.md`. Linux has no
|
|
93
|
+
mechanism in this build (`core/sandbox.ts`'s stated gap), so a Linux host
|
|
94
|
+
refuses rather than running unconfined. Inbound sockets are not denied. The
|
|
95
|
+
environment allow-list is a control over NAMES and therefore best-effort by
|
|
96
|
+
construction; the load-bearing control beside it is that egress is denied, so a
|
|
97
|
+
secret that does reach the child has nowhere to go.
|
|
98
|
+
|
|
99
|
+
The read jail is a control over PATHS and has the same shape of limit: a secret
|
|
100
|
+
someone checked into the canonical workspace is inside a root the session may
|
|
101
|
+
read, and no sandbox rule will change that. What the jail buys is that
|
|
102
|
+
everything outside those two roots is unreadable whether or not anyone thought
|
|
103
|
+
to name it.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# The Codex workspace broker
|
|
2
|
+
|
|
3
|
+
`approval codex apply`, and the one-tool server `approval codex serve` in front
|
|
4
|
+
of it, are the executable half of APRV-325. `docs/codex-workspace-planner.md`
|
|
5
|
+
describes the read-only planner this is built on; that document's closing
|
|
6
|
+
warning, that snapshot checks establish no OS custody, is the gap this one
|
|
7
|
+
closes as far as a POSIX host allows and says plainly where it does not.
|
|
8
|
+
|
|
9
|
+
## What is fixed, and what a caller may say
|
|
10
|
+
|
|
11
|
+
Everything that decides authority comes from the instance manifest, which lives
|
|
12
|
+
under a root-owned install root (`docs/codex-enforced-session.md`,
|
|
13
|
+
`approval codex doctor --strict`):
|
|
14
|
+
|
|
15
|
+
| fact | source |
|
|
16
|
+
|---|---|
|
|
17
|
+
| acting identity | `agent:codex-<instance_id>` from the manifest |
|
|
18
|
+
| workspace root | `paths.workspace` |
|
|
19
|
+
| policy file | `paths.policy` |
|
|
20
|
+
| event log | `paths.log` |
|
|
21
|
+
| action class | derived from the path by the policy's `protected_paths` |
|
|
22
|
+
| reversibility | always `false` |
|
|
23
|
+
|
|
24
|
+
A caller supplies two things and nothing else: `operations`, and
|
|
25
|
+
`expected_policy_sha256`. Any other key is refused `input-invalid`. There is no
|
|
26
|
+
`--as`, no path, no class, no token and no sandbox argument in the published
|
|
27
|
+
tool schema, because none was ever published and so none had to be removed.
|
|
28
|
+
|
|
29
|
+
The server-side allowlist is positive and has one member. A name not on it is
|
|
30
|
+
refused whether or not anything advertised it, and the check runs at call time
|
|
31
|
+
as well as at list time.
|
|
32
|
+
|
|
33
|
+
## The order of one apply
|
|
34
|
+
|
|
35
|
+
1. **Verified log, attested policy.** The log is read under chain verification;
|
|
36
|
+
the policy file is read once and those exact bytes are hashed and checked
|
|
37
|
+
against the latest attestation. Unattested, drifted or unparseable policy
|
|
38
|
+
refuses before a plan exists.
|
|
39
|
+
2. **Expected digest.** A caller's `expected_policy_sha256` that differs from
|
|
40
|
+
the attested digest refuses `attestation-drift`: the proposal was built
|
|
41
|
+
against a policy nobody is enforcing.
|
|
42
|
+
3. **Plan.** `planWorkspaceProposal` does the path work. Traversal, absolute
|
|
43
|
+
paths, symlinks, hardlinked preimages, case aliases, missing sources,
|
|
44
|
+
existing destinations and preimage mismatches are refused, and a human-only
|
|
45
|
+
class is refused *before* its preimage is read.
|
|
46
|
+
4. **Register.** One action per distinct path class, under a task id derived
|
|
47
|
+
from the plan's payload hash. Classes are never collapsed: the class is what
|
|
48
|
+
the operator's roster, budget and autonomy are keyed to, and an action that
|
|
49
|
+
reports a cheaper class than it performs is exactly the self-report SPEC.md
|
|
50
|
+
§11.1 invariant 4 forbids.
|
|
51
|
+
5. **Authorize every leg.** Policy decides. A leg the policy sends to a human
|
|
52
|
+
refuses `approval-required` and names the action keys awaiting a decision;
|
|
53
|
+
nothing is written, and the identical proposal applies once a person has
|
|
54
|
+
granted. No grant is fabricated and no token is minted here.
|
|
55
|
+
6. **Start every leg before any byte moves.** A later leg refusing to start
|
|
56
|
+
closes the earlier ones `execution.failed` and returns `start-refused`. The
|
|
57
|
+
workspace is untouched by construction, not by cleanup.
|
|
58
|
+
7. **Custody, then revalidation under it.** The lock is taken first and the plan
|
|
59
|
+
is revalidated afterwards, because a revalidation that precedes the lock
|
|
60
|
+
proves only what was true before another writer could act.
|
|
61
|
+
8. **Stage, journal, apply.** New bytes and preimages are written and fsynced
|
|
62
|
+
into a staging directory on the same filesystem, then a journal naming the
|
|
63
|
+
before-state and the after-state of every endpoint is written and fsynced,
|
|
64
|
+
and only then does anything move.
|
|
65
|
+
9. **Read the workspace back.** The outcome is what the readback proved, never
|
|
66
|
+
what the applying code believed: a `rename` that returned zero and a `rename`
|
|
67
|
+
whose effect a crash lost look identical from inside the process that made
|
|
68
|
+
the call.
|
|
69
|
+
|
|
70
|
+
## The three outcomes
|
|
71
|
+
|
|
72
|
+
| readback | broker result | log |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| every endpoint is the after-state | `ok`, `state: "after"` | `execution.completed` on every leg |
|
|
75
|
+
| every endpoint is the before-state | `commit-not-applied` | `execution.failed` on every leg |
|
|
76
|
+
| anything else | `commit-unknown` | `execution.indeterminate`, reason `workspace-commit-unknown`, on every leg |
|
|
77
|
+
|
|
78
|
+
`workspace-commit-unknown` is new to SPEC.md §8's closed reason set and to
|
|
79
|
+
`schema/event.schema.json` (APRV-325.2). It is the local counterpart of
|
|
80
|
+
`act-threw`: POSIX offers no atomic multi-file rename, so a crash, a permission
|
|
81
|
+
error or a failed rollback can land between two of them. It is a separate member
|
|
82
|
+
rather than a reuse of the first because a reader deciding what to do next needs
|
|
83
|
+
to know whether the unknown effect was a remote call or a half-written tree.
|
|
84
|
+
|
|
85
|
+
A mixed commit retains both the transaction journal and the workspace lock, so
|
|
86
|
+
no further brokered change can run until a person has looked.
|
|
87
|
+
`approval codex recover` reads that journal and reports `before`, `after` or
|
|
88
|
+
`mixed`, and it changes nothing: rolling a mixed workspace forward would guess
|
|
89
|
+
which half the human approved, and rolling it back would delete the half that
|
|
90
|
+
committed. Resolution is `approval execution reconcile`, which is human-only.
|
|
91
|
+
|
|
92
|
+
## What custody does and does not prove
|
|
93
|
+
|
|
94
|
+
The broker takes an `O_CREAT | O_EXCL` lock on a reserved name in the workspace.
|
|
95
|
+
That excludes every other broker that respects it and nothing else. It then asks
|
|
96
|
+
POSIX ownership and mode whether any principal other than its own effective user
|
|
97
|
+
can write the workspace root or the directories the transaction touches.
|
|
98
|
+
|
|
99
|
+
- `os-exclusive`: the lock is held and POSIX says no other principal can write.
|
|
100
|
+
- `advisory`: the lock is held and that second claim failed or could not be made.
|
|
101
|
+
|
|
102
|
+
Every report also carries `acl-unproven`, because ownership and mode say nothing
|
|
103
|
+
about ACLs — the same honesty `approval codex doctor --strict` already keeps.
|
|
104
|
+
`--require-exclusive-custody` turns the weak answer into a refusal instead of a
|
|
105
|
+
footnote, and an installation that needs the strong claim sets it.
|
|
106
|
+
|
|
107
|
+
Two paths inside the workspace are reserved and can never be a proposal
|
|
108
|
+
endpoint: `.approval-codex-txn/` and `.approval-codex-lock`. Staging must share
|
|
109
|
+
a filesystem with the workspace for `rename` to be atomic, so it lives inside
|
|
110
|
+
it; reserving the names is what stops a proposal from writing its own journal.
|
|
111
|
+
|
|
112
|
+
## What this is not
|
|
113
|
+
|
|
114
|
+
This is a gate on **workspace writes**. It confines no shell, revokes no
|
|
115
|
+
credential and blocks no egress, and neither its presence nor a passing
|
|
116
|
+
`codex setup --check` makes a session enforced. A Codex session that can still
|
|
117
|
+
write the workspace by some other route is not bounded by this broker, and
|
|
118
|
+
proving that no such route exists is APRV-325.3's job, not this one's.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "approval-md",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Human approval for agent actions. CLI runtime for the approval.md convention (pre-release).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"LICENSE",
|
|
24
24
|
"NOTICE",
|
|
25
25
|
"docs/codex-enforced-session.md",
|
|
26
|
+
"docs/codex-workspace-broker.md",
|
|
26
27
|
"templates/codex"
|
|
27
28
|
],
|
|
28
29
|
"repository": "github:approval-md/approval.md",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"typecheck": "tsc --noEmit",
|
|
39
40
|
"test": "tsc -p tsconfig.json && node scripts/run-tests.mjs",
|
|
40
41
|
"lint": "oxlint src tests",
|
|
42
|
+
"agent-hours": "node scripts/agent-hours.mjs",
|
|
41
43
|
"ci:local": "node scripts/ci-local.mjs",
|
|
42
44
|
"check:tier": "node scripts/classify-tier.mjs",
|
|
43
45
|
"check:changed": "node scripts/classify-tier.mjs --working-tree --run",
|