@jaguilar87/gaia 5.0.7 → 5.0.9
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -0
- package/bin/README.md +6 -1
- package/bin/cli/approvals.py +486 -474
- package/bin/cli/brief.py +13 -0
- package/bin/cli/doctor.py +1 -1
- package/dist/gaia-ops/.claude-plugin/plugin.json +1 -1
- package/dist/gaia-ops/hooks/adapters/claude_code.py +92 -86
- package/dist/gaia-ops/hooks/modules/agents/handoff_persister.py +13 -2
- package/dist/gaia-ops/hooks/modules/context/context_injector.py +23 -7
- package/dist/gaia-ops/hooks/modules/events/event_writer.py +63 -96
- package/dist/gaia-ops/hooks/modules/security/__init__.py +0 -2
- package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +238 -69
- package/dist/gaia-ops/hooks/modules/security/approval_grants.py +506 -1103
- package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +24 -1
- package/dist/gaia-ops/hooks/modules/session/pending_scanner.py +150 -90
- package/dist/gaia-ops/hooks/modules/session/session_manifest.py +257 -28
- package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +19 -0
- package/dist/gaia-ops/hooks/post_compact.py +1 -0
- package/dist/gaia-ops/hooks/pre_compact.py +1 -0
- package/dist/gaia-ops/hooks/user_prompt_submit.py +20 -0
- package/dist/gaia-ops/skills/agent-approval-protocol/SKILL.md +50 -14
- package/dist/gaia-ops/skills/agent-approval-protocol/reference.md +16 -9
- package/dist/gaia-ops/skills/agent-protocol/examples.md +12 -1
- package/dist/gaia-ops/skills/gaia-patterns/reference.md +2 -2
- package/dist/gaia-ops/skills/orchestrator-present-approval/SKILL.md +69 -22
- package/dist/gaia-ops/skills/orchestrator-present-approval/reference.md +16 -3
- package/dist/gaia-ops/skills/orchestrator-present-approval/template.md +20 -14
- package/dist/gaia-ops/skills/pending-approvals/SKILL.md +16 -11
- package/dist/gaia-ops/skills/subagent-request-approval/SKILL.md +28 -3
- package/dist/gaia-ops/skills/subagent-request-approval/reference.md +34 -8
- package/dist/gaia-ops/tools/migration/README.md +10 -12
- package/dist/gaia-ops/tools/scan/orchestrator.py +194 -10
- package/dist/gaia-ops/tools/scan/tests/test_integration.py +1 -2
- package/dist/gaia-security/.claude-plugin/plugin.json +1 -1
- package/dist/gaia-security/hooks/adapters/claude_code.py +92 -86
- package/dist/gaia-security/hooks/modules/agents/handoff_persister.py +13 -2
- package/dist/gaia-security/hooks/modules/context/context_injector.py +23 -7
- package/dist/gaia-security/hooks/modules/events/event_writer.py +63 -96
- package/dist/gaia-security/hooks/modules/security/__init__.py +0 -2
- package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +238 -69
- package/dist/gaia-security/hooks/modules/security/approval_grants.py +506 -1103
- package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +24 -1
- package/dist/gaia-security/hooks/modules/session/pending_scanner.py +150 -90
- package/dist/gaia-security/hooks/modules/session/session_manifest.py +257 -28
- package/dist/gaia-security/hooks/modules/tools/bash_validator.py +19 -0
- package/dist/gaia-security/hooks/user_prompt_submit.py +20 -0
- package/gaia/approvals/__init__.py +2 -1
- package/gaia/approvals/store.py +165 -15
- package/gaia/store/schema.sql +38 -1
- package/gaia/store/writer.py +400 -0
- package/hooks/adapters/claude_code.py +92 -86
- package/hooks/elicitation_result.py +20 -75
- package/hooks/modules/agents/handoff_persister.py +13 -2
- package/hooks/modules/context/context_injector.py +23 -7
- package/hooks/modules/events/event_writer.py +63 -96
- package/hooks/modules/security/__init__.py +0 -2
- package/hooks/modules/security/approval_cleanup.py +238 -69
- package/hooks/modules/security/approval_grants.py +506 -1103
- package/hooks/modules/security/mutative_verbs.py +24 -1
- package/hooks/modules/session/pending_scanner.py +150 -90
- package/hooks/modules/session/session_manifest.py +257 -28
- package/hooks/modules/tools/bash_validator.py +19 -0
- package/hooks/post_compact.py +1 -0
- package/hooks/pre_compact.py +1 -0
- package/hooks/user_prompt_submit.py +20 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/bootstrap_database.sh +66 -17
- package/scripts/migrations/README.md +26 -14
- package/scripts/migrations/schema.checksum +2 -2
- package/scripts/migrations/v18_to_v19.sql +36 -0
- package/scripts/migrations/v19_to_v20.sql +20 -0
- package/skills/agent-approval-protocol/SKILL.md +50 -14
- package/skills/agent-approval-protocol/reference.md +16 -9
- package/skills/agent-protocol/examples.md +12 -1
- package/skills/gaia-patterns/reference.md +2 -2
- package/skills/orchestrator-present-approval/SKILL.md +69 -22
- package/skills/orchestrator-present-approval/reference.md +16 -3
- package/skills/orchestrator-present-approval/template.md +20 -14
- package/skills/pending-approvals/SKILL.md +16 -11
- package/skills/subagent-request-approval/SKILL.md +28 -3
- package/skills/subagent-request-approval/reference.md +34 -8
- package/tools/migration/README.md +10 -12
- package/tools/scan/orchestrator.py +194 -10
- package/tools/scan/tests/test_integration.py +1 -2
- package/bin/cli/plans.py +0 -517
- package/dist/gaia-ops/tools/context/deep_merge.py +0 -159
- package/dist/gaia-ops/tools/migration/migrate_04_harness_events.py +0 -132
- package/dist/gaia-ops/tools/migration/migrate_04_harness_events.sh +0 -23
- package/dist/gaia-ops/tools/scan/merge.py +0 -213
- package/dist/gaia-ops/tools/scan/tests/test_merge.py +0 -269
- package/gaia/approvals/revert.py +0 -282
- package/tools/context/deep_merge.py +0 -159
- package/tools/migration/migrate_04_harness_events.py +0 -132
- package/tools/migration/migrate_04_harness_events.sh +0 -23
- package/tools/scan/merge.py +0 -213
- package/tools/scan/tests/test_merge.py +0 -269
|
@@ -15,11 +15,13 @@ names the specific action. No exceptions. No brevity shortcuts.
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
`orchestrator-present-approval` is the discipline the orchestrator follows when
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
an approval needs the user's consent: relay the sealed fields into
|
|
19
|
+
AskUserQuestion -- mandatory fields in the question, mandatory nonce in the
|
|
20
|
+
option label. The orchestrator has no shell, so it never dispatches a subagent
|
|
21
|
+
to derive or verify an approval; it presents from a trusted source it already
|
|
22
|
+
holds. For the subagent side that produced the payload see
|
|
23
|
+
`subagent-request-approval`; for the data contract itself see
|
|
24
|
+
`agent-approval-protocol`.
|
|
23
25
|
|
|
24
26
|
## Mental Model
|
|
25
27
|
|
|
@@ -27,19 +29,53 @@ The orchestrator sits between the subagent and the user. The user cannot make
|
|
|
27
29
|
an informed decision on data they have not seen -- a summary, a reference to
|
|
28
30
|
"the plan above", or an offer to show details on request all push the decision
|
|
29
31
|
without the data needed to decide. The job is **verbatim relay, not
|
|
30
|
-
re-authoring**: rewriting any of the
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
**
|
|
32
|
+
re-authoring**: rewriting any of the sealed fields would change the consent
|
|
33
|
+
surface from what was recorded. Integrity of the payload is enforced at grant
|
|
34
|
+
**activation** (`verify_fingerprint` in `gaia/approvals/chain.py`, called when
|
|
35
|
+
the user selects the Approve label), not at presentation -- so presentation
|
|
36
|
+
itself never needs a verify-dispatch.
|
|
37
|
+
|
|
38
|
+
## Step 0 -- Present from a trusted source; never dispatch to verify or derive
|
|
39
|
+
|
|
40
|
+
The orchestrator has no shell. It MUST NOT dispatch a subagent solely to derive
|
|
41
|
+
or verify an approval before presenting -- that dispatch is both unnecessary
|
|
42
|
+
(the integrity check runs at activation, below) and harmful (its SubagentStop
|
|
43
|
+
can sweep the very pending being verified). Instead, present from one of two
|
|
44
|
+
**trusted** sources:
|
|
45
|
+
|
|
46
|
+
1. **Primary -- the injected `[PENDING-APPROVALS-VERIFIED]` block.** A per-turn
|
|
47
|
+
hook (`hooks/modules/session/session_manifest.py`) injects, on every
|
|
48
|
+
`UserPromptSubmit`, every pending that has survived >= 1 turn. Each row in
|
|
49
|
+
that block has already been DB-read and fingerprint-verified by the hook
|
|
50
|
+
(`build_verified_pending_approvals` -- only rows whose payload re-canonicalizes
|
|
51
|
+
to the fingerprint stored on their `REQUESTED` event appear, each marked
|
|
52
|
+
`verified: true`). **Present directly from this block** -- the fields, the
|
|
53
|
+
full `approval_id`, and (for batches) the whole `command_set` with its minted
|
|
54
|
+
id are all there. No DB query, no `derive-id`, no dispatch.
|
|
55
|
+
2. **Fallback -- same-turn relay.** A pending a subagent emits during the
|
|
56
|
+
CURRENT turn will not be in this turn's block yet: the block is built at
|
|
57
|
+
`UserPromptSubmit`, before the subagent ran. For that case present from the
|
|
58
|
+
subagent's relayed `approval_request`. This is justified because the pending
|
|
59
|
+
was freshly minted in THIS session by a trusted dispatch, AND integrity is
|
|
60
|
+
enforced at grant **activation** (`verify_fingerprint` fires when the user
|
|
61
|
+
selects the Approve label), not at presentation. The old pre-presentation
|
|
62
|
+
verify was redundant belt-and-suspenders; it is removed.
|
|
63
|
+
|
|
64
|
+
Once the pending survives a turn it appears in the injected block, so the relay
|
|
65
|
+
is only ever needed for the same-turn case.
|
|
66
|
+
|
|
67
|
+
**For a `command_set` (plan-first batch) you do not derive the id -- you read it
|
|
68
|
+
from the block.** The hook mints the `approval_id` at SubagentStop
|
|
69
|
+
(`_intake_command_set_pending` -- see Rule 3) from the **content** of the
|
|
70
|
+
command_set (`derive_command_set_id` in `gaia/approvals/store.py`,
|
|
71
|
+
`P-<first 32 hex of sha256(canonical(command list))>`). Once that pending has
|
|
72
|
+
survived a turn, the `[PENDING-APPROVALS-VERIFIED]` block carries it with its
|
|
73
|
+
minted `approval_id` and all N commands already attached -- so you read the id
|
|
74
|
+
and the commands straight from the block. **No `gaia approvals derive-id`
|
|
75
|
+
dispatch is needed.** For a command_set emitted in the CURRENT turn (not yet in
|
|
76
|
+
the block), present from the subagent's relayed `approval_request`, which carries
|
|
77
|
+
the same `command_set`; the content-derived id reaches you when the pending
|
|
78
|
+
appears in the next turn's block.
|
|
43
79
|
|
|
44
80
|
## Mandatory presentation -- 5 labeled fields + nonce-suffixed label
|
|
45
81
|
|
|
@@ -66,7 +102,13 @@ whose `id` starts with `P-{prefix}`. Without the suffix no grant is created.
|
|
|
66
102
|
See `template.md` for the canonical layout and `reference.md` -> "GOOD vs BAD
|
|
67
103
|
Examples" for full presentations.
|
|
68
104
|
|
|
69
|
-
Fields above are extracted from
|
|
105
|
+
Fields above are extracted from your trusted source. From the injected
|
|
106
|
+
`[PENDING-APPROVALS-VERIFIED]` block (the primary path) they appear under the
|
|
107
|
+
canonical names shown here (`operation`, `exact_content`, `scope`, `risk_level`,
|
|
108
|
+
`rationale`, `rollback_hint`). From a same-turn relayed `approval_request` (the
|
|
109
|
+
fallback) the rollback field arrives under the key `rollback` -- map it to
|
|
110
|
+
ROLLBACK the same way. Either way you copy values verbatim; you do not re-author
|
|
111
|
+
them.
|
|
70
112
|
|
|
71
113
|
## Rules
|
|
72
114
|
|
|
@@ -84,7 +126,12 @@ Fields above are extracted from the DB-stored canonical payload (`payload_json`
|
|
|
84
126
|
`APPROVAL_REQUEST` carrying a `command_set` of >= 2 `{command, rationale}`
|
|
85
127
|
items and **no** `approval_id`, the SubagentStop processor
|
|
86
128
|
(`handoff_persister._intake_command_set_pending`) mints ONE pending
|
|
87
|
-
`COMMAND_SET` with one `approval_id`.
|
|
129
|
+
`COMMAND_SET` with one content-derived `approval_id`. Once that pending has
|
|
130
|
+
survived a turn it appears in the injected `[PENDING-APPROVALS-VERIFIED]`
|
|
131
|
+
block with its minted `approval_id` and all N commands -- **read the id and
|
|
132
|
+
commands from the block; do not dispatch `gaia approvals derive-id`.** (A
|
|
133
|
+
command_set emitted in the current turn is presented from the subagent's
|
|
134
|
+
relayed `approval_request`.) You present that single approval: list
|
|
88
135
|
**all N commands** in the question body, but use **one** Approve label with
|
|
89
136
|
**one** `[P-{nonce8}]` suffix -- one consent covers the whole batch. On
|
|
90
137
|
approval, `activate_db_pending_by_prefix` Step 3b creates a single
|
|
@@ -120,5 +167,5 @@ wording, see `reference.md` -> "GOOD vs BAD Examples", "Option Label Patterns",
|
|
|
120
167
|
| "Similar command, slightly different path -- I'll reuse / wrap it" | Grants match the statement signature byte-for-byte. Any wrapper, redirect, flag, or path drift is a different signature and a fresh re-block. |
|
|
121
168
|
| "The same command emitted a new approval_id" | Grants are single-use and consumed on the first retry. A second run is a new APPROVAL_REQUEST -- approve again. |
|
|
122
169
|
| "I'll set batch_scope to approve many at once" | `batch_scope` is ignored -- but a real batch path exists: a plan-first `command_set` (>= 2 items, no `approval_id`) is intaken into ONE pending `COMMAND_SET`. Present that single approval (N commands shown, one `[P-...]` nonce, one consent), not N separate approvals. |
|
|
123
|
-
| "I can paraphrase a field before relaying" | The fingerprint covers all
|
|
124
|
-
| **"
|
|
170
|
+
| "I can paraphrase a field before relaying" | The fingerprint covers all sealed fields and is checked at grant **activation** (`verify_fingerprint`, when the user selects the Approve label); a paraphrase there raises `ChainTamperError` and the grant never forms. Relay verbatim so activation succeeds. |
|
|
171
|
+
| **"I'll dispatch a subagent to verify or derive the approval before presenting"** | The orchestrator has no shell and must NEVER dispatch to verify or derive an approval. The pending arrives **already verified** in the injected `[PENDING-APPROVALS-VERIFIED]` block (DB-read + fingerprint-checked by the per-turn hook, `verified: true`) -- present from it. For a same-turn pending not yet in the block, present from the subagent's relayed `approval_request`. A verify/derive dispatch is unnecessary (integrity is enforced at activation) and harmful (its SubagentStop can sweep the very pending). For `command_set`, read the minted `approval_id` and all commands from the block -- do not run `gaia approvals derive-id`. |
|
|
@@ -151,6 +151,16 @@ commands** in the question body, with **one** Approve label carrying **one**
|
|
|
151
151
|
`[P-{nonce8}]` suffix. The user gives one consent; each command then runs on its
|
|
152
152
|
own retry within the 60-minute window. You do NOT issue N separate approvals.
|
|
153
153
|
|
|
154
|
+
**Reading the batch id and commands -- from the block, not by dispatch.** Once
|
|
155
|
+
the minted `COMMAND_SET` pending has survived a turn, it appears in the injected
|
|
156
|
+
`[PENDING-APPROVALS-VERIFIED]` block with its content-derived `approval_id` and
|
|
157
|
+
all N commands attached (`build_verified_pending_approvals` in
|
|
158
|
+
`hooks/modules/session/session_manifest.py`). Read the id and the commands
|
|
159
|
+
straight from that block -- the orchestrator has no shell and must NOT dispatch
|
|
160
|
+
`gaia approvals derive-id` or any verify command. For a command_set emitted in
|
|
161
|
+
the CURRENT turn (not yet in the block), present from the subagent's relayed
|
|
162
|
+
`approval_request`, which carries the same `command_set`.
|
|
163
|
+
|
|
154
164
|
## Grant Activation Mechanics
|
|
155
165
|
|
|
156
166
|
When the hook blocks a T3 Bash command in subagent context,
|
|
@@ -161,9 +171,12 @@ generates a `P-{uuid4_hex}` `approval_id`, fingerprints the payload, inserts an
|
|
|
161
171
|
message ends with `approval_id: P-{...}` (`build_t3_blocked_denial_message` in
|
|
162
172
|
`hooks/modules/security/approval_messages.py`).
|
|
163
173
|
|
|
164
|
-
The
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
The orchestrator presents via AskUserQuestion with the `[P-xxxxxxxx]` label,
|
|
175
|
+
reading the `approval_id` and fields from the injected
|
|
176
|
+
`[PENDING-APPROVALS-VERIFIED]` block (primary) or, for a same-turn pending not
|
|
177
|
+
yet in the block, from the subagent's relayed `approval_request` (fallback). It
|
|
178
|
+
does not dispatch to verify or derive. When the user selects the Approve label,
|
|
179
|
+
the **ElicitationResult hook**
|
|
167
180
|
(`hooks/elicitation_result.py`) fires and calls
|
|
168
181
|
`activate_db_pending_by_prefix()`, which:
|
|
169
182
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# AskUserQuestion Template
|
|
2
2
|
|
|
3
3
|
Use this layout verbatim when presenting an approval to the user. Replace
|
|
4
|
-
`{...}` placeholders with values
|
|
5
|
-
|
|
4
|
+
`{...}` placeholders with values read from your trusted source -- the injected
|
|
5
|
+
`[PENDING-APPROVALS-VERIFIED]` block (primary; already DB-read and
|
|
6
|
+
fingerprint-verified by the per-turn hook) or, for a same-turn pending not yet
|
|
7
|
+
in the block, the subagent's relayed `approval_request` (fallback). Never
|
|
8
|
+
dispatch a subagent to derive or verify the approval. Do not paraphrase,
|
|
9
|
+
summarize, or omit any field.
|
|
6
10
|
|
|
7
11
|
## Standard Approval (single command)
|
|
8
12
|
|
|
@@ -23,19 +27,21 @@ AskUserQuestion(
|
|
|
23
27
|
)
|
|
24
28
|
```
|
|
25
29
|
|
|
26
|
-
Where `approval_id_prefix8` is the first 8 characters
|
|
27
|
-
|
|
30
|
+
Where `approval_id_prefix8` is the first 8 characters (after the `P-` prefix) of
|
|
31
|
+
the `approval_id` read from the `[PENDING-APPROVALS-VERIFIED]` block, or from the
|
|
32
|
+
subagent's `approval_request` for a same-turn pending.
|
|
28
33
|
|
|
29
|
-
##
|
|
34
|
+
## Batch template (COMMAND_SET)
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
36
|
+
When the subagent emits a plan-first `APPROVAL_REQUEST` with a `command_set`
|
|
37
|
+
of >= 2 `{command, rationale}` items and **no** `approval_id`, the
|
|
38
|
+
SubagentStop intake mints ONE pending `COMMAND_SET` approval. Present it as
|
|
39
|
+
a single approval: list all N commands in the question body, one Approve
|
|
40
|
+
label with one `[P-{nonce8}]` suffix. See `reference.md` -> "On batch
|
|
41
|
+
intents" for the full layout.
|
|
42
|
+
|
|
43
|
+
A `batch_scope` field and the word "batch" in an option label are both
|
|
44
|
+
ignored -- the signal is the presence of `command_set` in the contract.
|
|
39
45
|
|
|
40
46
|
## Field Extraction Reference
|
|
41
47
|
|
|
@@ -46,4 +52,4 @@ approval (the template above), once per `approval_id`. See `reference.md` ->
|
|
|
46
52
|
| SCOPE | `sealed_payload.scope` |
|
|
47
53
|
| RIESGO | `sealed_payload.risk_level` + `sealed_payload.rationale` |
|
|
48
54
|
| ROLLBACK | `sealed_payload.rollback_hint` (null -> "NOT REVERSIBLE") |
|
|
49
|
-
| Option nonce suffix | `
|
|
55
|
+
| Option nonce suffix | `approval_id` first 8 chars after `P-` (from the `[PENDING-APPROVALS-VERIFIED]` block, or `approval_request.approval_id` for a same-turn pending) |
|
|
@@ -37,7 +37,7 @@ report "rejected" when nothing actually changed.
|
|
|
37
37
|
| `gaia approvals list` | DB grants + filesystem pendings | `cmd_list` (mixed) |
|
|
38
38
|
| `gaia approvals reject NONCE` | filesystem only | `reject_pending` in `hooks/modules/security/approval_grants.py` |
|
|
39
39
|
| `gaia approvals reject-all` | filesystem only | loops `reject_pending` |
|
|
40
|
-
| `gaia approvals clean` | filesystem
|
|
40
|
+
| `gaia approvals clean` | DB (cross-session stale pendings) + filesystem | `cmd_clean` in `bin/cli/approvals.py`: calls `store.list_pending(all_sessions=True)`, transitions every pending older than `DEFAULT_PENDING_TTL_MINUTES` (24 h) to `revoked` via `store.revoke()`, then calls `cleanup_expired_grants` for filesystem files |
|
|
41
41
|
|
|
42
42
|
The practical consequence: `revoke` is the DB-aware single-id verb; `reject` and
|
|
43
43
|
`reject-all` only touch the legacy filesystem queue. If you need to mark a DB
|
|
@@ -105,15 +105,19 @@ Offer bulk cleanup when the user says "limpia todos los pendings", "borra los
|
|
|
105
105
|
pendientes", or when SessionStart surfaces 5+ orphaned pendings the user has
|
|
106
106
|
not engaged with.
|
|
107
107
|
|
|
108
|
-
- `gaia approvals reject-all` -- bulk reject across the **filesystem** queue.
|
|
109
|
-
Returns "0 rejected" when the queue is empty.
|
|
110
|
-
- `gaia approvals clean` --
|
|
108
|
+
- `gaia approvals reject-all` -- bulk soft-reject across the **filesystem** queue.
|
|
109
|
+
Returns "0 rejected" when the queue is empty. Does not touch DB rows.
|
|
110
|
+
- `gaia approvals clean` -- the first-class cross-session bulk drain for stale
|
|
111
|
+
DB pendings: `cmd_clean` calls `store.list_pending(all_sessions=True)` and
|
|
112
|
+
transitions every pending older than 24 h (`DEFAULT_PENDING_TTL_MINUTES`) to
|
|
113
|
+
`revoked` via `store.revoke()`, then runs `cleanup_expired_grants` to clean
|
|
114
|
+
expired filesystem grant files. Runs without a T3 prompt (consent-reducing,
|
|
115
|
+
listed in `CONSENT_REDUCING_SUBCOMMAND_EXCEPTIONS`). Use this when
|
|
116
|
+
`gaia approvals pending --all-sessions` shows a backlog of stale rows.
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
done" after `reject-all` if the DB queue still has pending rows -- check
|
|
116
|
-
`gaia approvals pending --all-sessions` to confirm.
|
|
118
|
+
Do not report "bulk cleanup done" after `reject-all` alone -- it only clears
|
|
119
|
+
the filesystem queue. Run `gaia approvals clean` to drain the DB backlog, then
|
|
120
|
+
confirm with `gaia approvals pending --all-sessions`.
|
|
117
121
|
|
|
118
122
|
Do not offer `reject-all` when there are active same-session pendings the user
|
|
119
123
|
may still want to approve.
|
|
@@ -123,8 +127,9 @@ may still want to approve.
|
|
|
123
127
|
- Approving without showing the exact COMANDO -- the user consents on the
|
|
124
128
|
verbatim string, not a summary. The full presentation discipline lives in
|
|
125
129
|
`orchestrator-present-approval`; this skill does not restate it.
|
|
126
|
-
- Treating `gaia approvals reject-all` as a
|
|
127
|
-
filesystem queue only
|
|
130
|
+
- Treating `gaia approvals reject-all` as a full cleanup -- it operates on the
|
|
131
|
+
filesystem queue only; DB rows survive the call. Use `gaia approvals clean`
|
|
132
|
+
to drain the DB backlog.
|
|
128
133
|
- Reporting "rechazado" without verifying the store -- `revoke` returns
|
|
129
134
|
`not_found` for filesystem-only pendings; the inverse happens for `reject` on
|
|
130
135
|
DB rows. Pick the verb by store, or be ready to fall back.
|
|
@@ -44,9 +44,20 @@ Add an `approval_request` to your `agent_contract_handoff`, copying the hook's f
|
|
|
44
44
|
|
|
45
45
|
The `approval_request` schema is canonical in `agent-approval-protocol` — relay the sealed_payload fields verbatim (the hook built them) and add `verification` (your own success criteria) + `approval_id` (the literal token from the denial). See `agent-approval-protocol/SKILL.md` for the full field list and types.
|
|
46
46
|
|
|
47
|
-
The `approval_id` is the `P-{...}` token
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
The `approval_id` is the `P-{...}` token tying this request to its `REQUESTED`
|
|
48
|
+
row in the DB. Fields written only in prose are invisible to the presentation --
|
|
49
|
+
the user would approve blind.
|
|
50
|
+
|
|
51
|
+
**What your relay is for: same-turn immediacy.** Your `approval_request` is the
|
|
52
|
+
orchestrator's source only for the CURRENT turn. The orchestrator's primary
|
|
53
|
+
source is the per-turn `[PENDING-APPROVALS-VERIFIED]` block injected at
|
|
54
|
+
`UserPromptSubmit`, which carries every pending that has survived >= 1 turn,
|
|
55
|
+
already DB-read and fingerprint-verified. But that block was built before you
|
|
56
|
+
ran this turn, so a pending you mint now is not in it yet -- the orchestrator
|
|
57
|
+
presents it from your relay until the next turn's block picks it up. You emit the
|
|
58
|
+
same fields either way; nothing on your side changes. The orchestrator never
|
|
59
|
+
dispatches a subagent to verify or derive your request -- integrity is enforced
|
|
60
|
+
at grant activation, not at presentation.
|
|
50
61
|
|
|
51
62
|
## Non-negotiable rules
|
|
52
63
|
|
|
@@ -99,6 +110,20 @@ with one `approval_id` -- so a batch of N commands is **one consent, N
|
|
|
99
110
|
commands**, not N approvals. A set of `<= 1` item is not a batch: it does not
|
|
100
111
|
mint a COMMAND_SET (use the normal singular block path for a single command).
|
|
101
112
|
|
|
113
|
+
You still emit the `command_set` with **no `approval_id`** -- nothing changes on
|
|
114
|
+
your side. What changed underneath: the minted `approval_id` is now
|
|
115
|
+
**content-derived** from the command_set
|
|
116
|
+
(`derive_command_set_id` -> `P-<first 32 hex of sha256(canonical commands)>`),
|
|
117
|
+
not a random uuid4. You do not compute or emit it (you cannot hash reliably, and
|
|
118
|
+
you have nothing to attempt yet); the value is purely internal. The reason it
|
|
119
|
+
matters: the content-derived id is reproducible without a uuid4 that could be
|
|
120
|
+
lost across sessions. Once the minted pending has survived a turn, the
|
|
121
|
+
orchestrator reads it -- with all N commands -- straight from the injected
|
|
122
|
+
`[PENDING-APPROVALS-VERIFIED]` block (no DB search, no derive-dispatch); for the
|
|
123
|
+
turn you mint it in, the orchestrator presents from the `command_set` in your
|
|
124
|
+
relay. Your contract stays the same -- `command_set` of `{command, rationale}`
|
|
125
|
+
items, no `approval_id`.
|
|
126
|
+
|
|
102
127
|
On the user's approval, that one pending activates into a single `COMMAND_SET`
|
|
103
128
|
grant (60-minute TTL); each item is then consumed byte-for-byte on its own
|
|
104
129
|
retry, with replay protection, until the whole set is `CONSUMED`. See
|
|
@@ -16,8 +16,12 @@ payload from the intercepted command and calls
|
|
|
16
16
|
3. writes the `REQUESTED` event to the DB.
|
|
17
17
|
|
|
18
18
|
The block message you receive (`[T3_BLOCKED] ...`) ends with `approval_id: P-{...}`.
|
|
19
|
-
You relay that token plus the operation details
|
|
20
|
-
|
|
19
|
+
You relay that token plus the operation details. For the current turn the
|
|
20
|
+
orchestrator presents from your relay; once the pending survives a turn it
|
|
21
|
+
appears in the per-turn `[PENDING-APPROVALS-VERIFIED]` block, already
|
|
22
|
+
fingerprint-verified by the hook. Payload integrity is enforced at grant
|
|
23
|
+
activation (`verify_fingerprint`), so the orchestrator never dispatches to
|
|
24
|
+
verify or derive your request.
|
|
21
25
|
|
|
22
26
|
Source: `bash_validator._build_sealed_payload()`, the subagent block path in
|
|
23
27
|
`bash_validator._validate_single_command()`; `gaia/approvals/store.py`
|
|
@@ -94,6 +98,24 @@ COMMAND_SET is ever minted for one command). The intake runs independently of
|
|
|
94
98
|
the audit handoff-row write, so a batch consent is never lost to an unrelated
|
|
95
99
|
DB failure.
|
|
96
100
|
|
|
101
|
+
**The COMMAND_SET `approval_id` is content-derived, not uuid4.** Unlike the
|
|
102
|
+
singular hook-block path (which mints `P-{uuid4hex}`), the intake derives the id
|
|
103
|
+
from the command_set content via `gaia.approvals.store.derive_command_set_id()`:
|
|
104
|
+
`P-<first 32 hex of sha256(canonical(post-filter command strings))>`. It then
|
|
105
|
+
passes that id to `insert_requested(..., approval_id=...)` as the pending row id.
|
|
106
|
+
The point is reproducibility without a fragile uuid4: a uuid4 minted at
|
|
107
|
+
SubagentStop could not be recovered by the parent (Claude Code #5812), but a
|
|
108
|
+
content-derived id needs no recovery -- the same canonicalization
|
|
109
|
+
(`chain.canonical_payload`) and mutative filter always yield the same id. Once
|
|
110
|
+
the minted pending survives a turn, the orchestrator reads that id (and all N
|
|
111
|
+
commands) straight from the injected `[PENDING-APPROVALS-VERIFIED]` block -- no
|
|
112
|
+
DB lookup and no `gaia approvals derive-id` dispatch; for the mint turn it
|
|
113
|
+
presents from the `command_set` in your relay. The id is
|
|
114
|
+
**order-sensitive** (the consume side matches positionally) and **content-only**
|
|
115
|
+
(rationale/session/agent are not folded in, so both sides agree from the command
|
|
116
|
+
list alone). Idempotency follows the existing fingerprint dedup: two identical
|
|
117
|
+
command sets map to one id.
|
|
118
|
+
|
|
97
119
|
**Envelope shape.** The sealed_payload the intake writes carries a `command_set`
|
|
98
120
|
key holding the verbatim list of `{command, rationale}` items, and `commands`
|
|
99
121
|
listing every command string in the set:
|
|
@@ -138,13 +160,17 @@ single-use within the 60-minute window.
|
|
|
138
160
|
Always `plan_status: "APPROVAL_REQUEST"`. The presence of `approval_id` tells the
|
|
139
161
|
orchestrator which path:
|
|
140
162
|
|
|
141
|
-
- **With `approval_id`** -- the hook blocked a single command; orchestrator
|
|
142
|
-
|
|
143
|
-
|
|
163
|
+
- **With `approval_id`** -- the hook blocked a single command; the orchestrator
|
|
164
|
+
presents from your relay (current turn) or the injected
|
|
165
|
+
`[PENDING-APPROVALS-VERIFIED]` block (later turns), and the single-use semantic
|
|
166
|
+
grant activates on user approval (fingerprint checked at activation).
|
|
144
167
|
- **Without `approval_id`, with a `command_set` of >= 2 items** -- plan-first
|
|
145
|
-
batch. The SubagentStop intake processor mints ONE pending `COMMAND_SET`
|
|
146
|
-
|
|
147
|
-
|
|
168
|
+
batch. The SubagentStop intake processor mints ONE pending `COMMAND_SET` with a
|
|
169
|
+
**content-derived** id (`derive_command_set_id`). The orchestrator reads that
|
|
170
|
+
id and the N commands from the injected `[PENDING-APPROVALS-VERIFIED]` block
|
|
171
|
+
(no derive-dispatch), or, for the mint turn, from the `command_set` in your
|
|
172
|
+
relay, then presents the single approval (N commands, one nonce). See
|
|
173
|
+
"Batch / COMMAND_SET -- wired" above.
|
|
148
174
|
- **Without `approval_id` and without a multi-item `command_set`** -- plan-first
|
|
149
175
|
single (you are presenting one T3 plan before attempting); the orchestrator
|
|
150
176
|
gates on user consent before any execution.
|
|
@@ -19,7 +19,12 @@ desde el filesystem hacia `~/.gaia/gaia.db`.
|
|
|
19
19
|
| 01 | Episodes | `.claude/project-context/episodic-memory/episodes.jsonl` | `episodes` (+`episodes_fts`) |
|
|
20
20
|
| 02 | Memory | `~/.claude/projects/-home-jorge-ws-me/memory/*.md` | `memory` (+`memory_fts`) |
|
|
21
21
|
| 03 | Context contracts | `.claude/project-context/project-context.json` | `context_contracts` |
|
|
22
|
-
| 04 | Harness events |
|
|
22
|
+
| 04 | Harness events | ~~`.claude/events/events.jsonl`~~ (ELIMINADO) | `harness_events` |
|
|
23
|
+
|
|
24
|
+
> **Dominio 04 completado y eliminado.** `events.jsonl` y su archivo `.lock` fueron
|
|
25
|
+
> retirados. El hook `event_writer` escribe directamente a `harness_events` en la DB.
|
|
26
|
+
> El script `migrate_04_harness_events.py` y su wrapper `.sh` fueron borrados una vez
|
|
27
|
+
> completada la absorción. Los datos vivos se leen desde `harness_events` en `~/.gaia/gaia.db`.
|
|
23
28
|
|
|
24
29
|
Cada dominio tiene 2 archivos:
|
|
25
30
|
|
|
@@ -37,8 +42,8 @@ bootstrap.sh # crea/inicializa ~/.gaia/gaia.db con s
|
|
|
37
42
|
./migrate_01_episodes.sh # ~50-80 MB de SQL, batch 80
|
|
38
43
|
./migrate_02_memory.sh # 28 .md (MEMORY.md excluido)
|
|
39
44
|
./migrate_03_context_contracts.sh # 12 secciones
|
|
40
|
-
|
|
41
|
-
./validate.sh #
|
|
45
|
+
# migrate_04_harness_events.sh ELIMINADO — dominio 04 completado; eventos en DB-canonical
|
|
46
|
+
./validate.sh # aserciones read-only (V4 eliminada junto con 04)
|
|
42
47
|
```
|
|
43
48
|
|
|
44
49
|
Cada script imprime `[migrate_NN] OK` al terminar.
|
|
@@ -50,14 +55,7 @@ Cada script imprime `[migrate_NN] OK` al terminar.
|
|
|
50
55
|
| 01 episodes | `INSERT OR IGNORE` (PK = `episode_id`) | sí |
|
|
51
56
|
| 02 memory | `INSERT OR IGNORE` (PK = `(project, name)`) | sí |
|
|
52
57
|
| 03 context_contracts | `INSERT OR IGNORE` (PK = `(project, section_name)`) | sí |
|
|
53
|
-
| 04 harness_events |
|
|
54
|
-
|
|
55
|
-
Para re-ejecutar 04 limpiamente:
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
sqlite3 ~/.gaia/gaia.db "DELETE FROM harness_events WHERE project='me';"
|
|
59
|
-
./migrate_04_harness_events.sh
|
|
60
|
-
```
|
|
58
|
+
| 04 harness_events | N/A — tool eliminado; escritura vía `event_writer` DB-direct | N/A |
|
|
61
59
|
|
|
62
60
|
## Validación
|
|
63
61
|
|
|
@@ -68,7 +66,7 @@ sqlite3 ~/.gaia/gaia.db "DELETE FROM harness_events WHERE project='me';"
|
|
|
68
66
|
| V1 | `COUNT(*) FROM episodes` == líneas no vacías de `episodes.jsonl` |
|
|
69
67
|
| V2 | `COUNT(*) FROM memory` == archivos `.md` (excluyendo `MEMORY.md`) |
|
|
70
68
|
| V3 | `COUNT(*) FROM context_contracts` == 12 |
|
|
71
|
-
| V4 |
|
|
69
|
+
| ~~V4~~ | ~~`COUNT(*) FROM harness_events` == líneas no vacías de `events.jsonl`~~ — eliminado junto con el dominio 04 |
|
|
72
70
|
| V5 | `COUNT(*) FROM episodes_fts` == `COUNT(*) FROM episodes` (FTS sync) |
|
|
73
71
|
|
|
74
72
|
Exit code: 0 si todas pasan, 1 si alguna falla.
|