approval-md 0.4.5 → 0.4.7

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.
Files changed (83) hide show
  1. package/SPEC.md +24 -3
  2. package/dist/src/channels/contract.d.ts +15 -0
  3. package/dist/src/channels/contract.js +23 -1
  4. package/dist/src/channels/contract.js.map +1 -1
  5. package/dist/src/channels/telegram.d.ts +180 -33
  6. package/dist/src/channels/telegram.js +256 -87
  7. package/dist/src/channels/telegram.js.map +1 -1
  8. package/dist/src/cli/channel-telegram.d.ts +114 -35
  9. package/dist/src/cli/channel-telegram.js +259 -120
  10. package/dist/src/cli/channel-telegram.js.map +1 -1
  11. package/dist/src/cli/doctor.js +33 -13
  12. package/dist/src/cli/doctor.js.map +1 -1
  13. package/dist/src/cli/help.d.ts +2 -0
  14. package/dist/src/cli/help.js +39 -3
  15. package/dist/src/cli/help.js.map +1 -1
  16. package/dist/src/cli/hook-exec-code.d.ts +150 -0
  17. package/dist/src/cli/hook-exec-code.js +357 -0
  18. package/dist/src/cli/hook-exec-code.js.map +1 -0
  19. package/dist/src/cli/hook.d.ts +154 -1
  20. package/dist/src/cli/hook.js +2344 -111
  21. package/dist/src/cli/hook.js.map +1 -1
  22. package/dist/src/cli/instructions.js +9 -6
  23. package/dist/src/cli/instructions.js.map +1 -1
  24. package/dist/src/cli/main.js +9 -0
  25. package/dist/src/cli/main.js.map +1 -1
  26. package/dist/src/cli/study.d.ts +22 -0
  27. package/dist/src/cli/study.js +153 -0
  28. package/dist/src/cli/study.js.map +1 -0
  29. package/dist/src/cli/verb-registry.d.ts +14 -0
  30. package/dist/src/cli/verb-registry.js +37 -0
  31. package/dist/src/cli/verb-registry.js.map +1 -1
  32. package/dist/src/cli/wordmark.d.ts +2 -2
  33. package/dist/src/cli/wordmark.js +2 -2
  34. package/dist/src/core/command-class.d.ts +349 -1
  35. package/dist/src/core/command-class.js +2347 -87
  36. package/dist/src/core/command-class.js.map +1 -1
  37. package/dist/src/core/exec-code.d.ts +77 -0
  38. package/dist/src/core/exec-code.js +60 -0
  39. package/dist/src/core/exec-code.js.map +1 -0
  40. package/dist/src/core/gate.d.ts +38 -0
  41. package/dist/src/core/gate.js +26 -0
  42. package/dist/src/core/gate.js.map +1 -1
  43. package/dist/src/core/git-push-argv.d.ts +111 -0
  44. package/dist/src/core/git-push-argv.js +375 -0
  45. package/dist/src/core/git-push-argv.js.map +1 -0
  46. package/dist/src/core/harness-version.d.ts +71 -30
  47. package/dist/src/core/harness-version.js +101 -34
  48. package/dist/src/core/harness-version.js.map +1 -1
  49. package/dist/src/core/policy-load.d.ts +8 -0
  50. package/dist/src/core/policy-load.js.map +1 -1
  51. package/dist/src/core/sender-identity.d.ts +13 -1
  52. package/dist/src/core/sender-identity.js +15 -0
  53. package/dist/src/core/sender-identity.js.map +1 -1
  54. package/dist/src/core/study-withdraw.d.ts +137 -0
  55. package/dist/src/core/study-withdraw.js +280 -0
  56. package/dist/src/core/study-withdraw.js.map +1 -0
  57. package/dist/src/daemon/git-evidence.d.ts +2 -2
  58. package/dist/src/daemon/git-evidence.js +1 -1
  59. package/dist/src/mcp/server.js +1 -1
  60. package/dist/src/mcp/server.js.map +1 -1
  61. package/docs/README-extended.md +3 -2
  62. package/docs/cli-reference.md +83 -7
  63. package/package.json +2 -2
  64. package/schema/event.schema.json +213 -1
  65. package/schema/fixtures/event/invalid/approval-granted-study.json +23 -0
  66. package/schema/fixtures/event/invalid/approval-rejected-looks-wrong-cli-channel.json +23 -0
  67. package/schema/fixtures/event/invalid/approval-rejected-looks-wrong-operator.json +24 -0
  68. package/schema/fixtures/event/invalid/approval-rejected-study-extra-key.json +24 -0
  69. package/schema/fixtures/event/invalid/approval-rejected-study-unknown-reason.json +23 -0
  70. package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-email-operator.json +20 -0
  71. package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-no-operator.json +19 -0
  72. package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-no-ref.json +19 -0
  73. package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-wrong-actor.json +20 -0
  74. package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-wrong-channel.json +20 -0
  75. package/schema/fixtures/event/invalid/approval-revoked-study.json +20 -0
  76. package/schema/fixtures/event/valid/approval-rejected-looks-wrong.json +23 -0
  77. package/schema/fixtures/event/valid/approval-rejected-study-withdrawn.json +20 -0
  78. package/schema/fixtures/policy/invalid/exec-code-custody-boolean.json +11 -0
  79. package/schema/fixtures/policy/invalid/exec-code-execute-code-value.json +11 -0
  80. package/schema/fixtures/policy/invalid/exec-code-unknown-key.json +12 -0
  81. package/schema/fixtures/policy/valid/exec-code-both.json +17 -0
  82. package/schema/fixtures/policy/valid/exec-code-custody.json +11 -0
  83. package/schema/policy.schema.json +23 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The `exec.code` gate: what the attested policy says about opaque agent code
3
+ * (APRV-510, 0.4.5 candidate).
4
+ *
5
+ * `python3 -c`, `node -e` and kin, an interpreter running a file that is not a
6
+ * skill script, and a Hermes `execute_code` call are all programs whose effect
7
+ * no classifier reads. Before this module every one of them was refused before
8
+ * policy (`hook-opaque`, `hook-unclassified`, `hook-hermes-execute-code-unbound`),
9
+ * and that stays the answer for every policy that does not speak: the gate is
10
+ * CLOSED unless the operator's policy opens it, key by key.
11
+ *
12
+ * Two keys, each opening one thing, and an absent key is the closed answer:
13
+ *
14
+ * - `exec.code.custody: attested` opens the class for the shell forms. It is the
15
+ * operator's statement that credential custody is in place: opaque code run by
16
+ * an agent that can read the tenant's credentials is a way round every gate
17
+ * that guards them, so the class may not exist without that statement.
18
+ * - `exec.code.execute_code: hooked`, beside the first, opens Hermes's
19
+ * `execute_code`. It is the operator's statement that a tool call made from
20
+ * inside the `execute_code` kernel re-fires the hook, which only a live probe
21
+ * can establish (`docs/hermes-hook.md`); without it one `execute_code` call
22
+ * would be an unbounded bypass of the adapter.
23
+ *
24
+ * Pure: a function of the parsed policy and nothing else. It decides whether a
25
+ * call may be CLASSIFIED `exec.code`; what the class then resolves to is the
26
+ * policy's ordinary class resolution (an `exec.code` row, an `exec.*` row, or
27
+ * `defaults.autonomy`), unchanged.
28
+ */
29
+ import type { Policy } from "./policy-load.js";
30
+ /** The `exec` policy block (schema/policy.schema.json, APRV-510). */
31
+ export interface ExecBlock {
32
+ code?: {
33
+ custody?: "attested" | "unattested";
34
+ execute_code?: "hooked" | "unhooked";
35
+ };
36
+ }
37
+ /** The class an opened program is judged under. Mirrors `command-class.ts`. */
38
+ export declare const EXEC_CODE_POLICY_CLASS = "exec.code";
39
+ /** The policy line that opens the class, as a refusal names it. */
40
+ export declare const EXEC_CODE_CUSTODY_KEY = "exec.code.custody: attested";
41
+ /** The policy line that opens Hermes `execute_code`, as a refusal names it. */
42
+ export declare const EXEC_CODE_EXECUTE_KEY = "exec.code.execute_code: hooked";
43
+ /**
44
+ * Why the gate is closed, which decides the refusal's code and words.
45
+ *
46
+ * - `silent`: the policy says nothing about `exec.code` (no `exec` block and no
47
+ * `exec.code` class row), or there is no policy. The refusal is today's,
48
+ * byte for byte.
49
+ * - `unattested`: the policy names `exec.code` (an `exec` block, or a class row
50
+ * for it) without `custody: attested`. The refusal is
51
+ * `hook-exec-code-custody-unattested`, naming the key.
52
+ * - `window`: an open window is standing. A window suspends the policy's
53
+ * answer, and opening this class is a policy statement, so it is not reached
54
+ * through one; the refusal is today's.
55
+ * - `harness`: the adapter binds native bytes this class's payload cannot
56
+ * carry (Codex), so the class is not opened there; the refusal is today's.
57
+ */
58
+ export type ExecCodeClosed = "silent" | "unattested" | "window" | "harness";
59
+ export interface ExecCodeGate {
60
+ /** May a shell segment be classified `exec.code`? */
61
+ shell: boolean;
62
+ /** May a Hermes `execute_code` call be classified `exec.code`? */
63
+ executeCode: boolean;
64
+ /** Why `shell` is false; `null` when it is true. */
65
+ closed: ExecCodeClosed | null;
66
+ }
67
+ /** The gate with everything closed for `reason`. */
68
+ export declare function closedExecCodeGate(reason: ExecCodeClosed): ExecCodeGate;
69
+ /**
70
+ * Does the policy name `exec.code` anywhere? An `exec` block, or a `classes`
71
+ * key that is exactly `exec.code`. A family row (`exec.*`) is not a mention:
72
+ * it was written for `exec.local`, and a policy that predates the class keeps
73
+ * its refusals byte for byte.
74
+ */
75
+ export declare function execCodeMentioned(policy: Policy): boolean;
76
+ /** What `policy` opens. `null` (no policy loaded) opens nothing. */
77
+ export declare function execCodeGate(policy: Policy | null): ExecCodeGate;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The `exec.code` gate: what the attested policy says about opaque agent code
3
+ * (APRV-510, 0.4.5 candidate).
4
+ *
5
+ * `python3 -c`, `node -e` and kin, an interpreter running a file that is not a
6
+ * skill script, and a Hermes `execute_code` call are all programs whose effect
7
+ * no classifier reads. Before this module every one of them was refused before
8
+ * policy (`hook-opaque`, `hook-unclassified`, `hook-hermes-execute-code-unbound`),
9
+ * and that stays the answer for every policy that does not speak: the gate is
10
+ * CLOSED unless the operator's policy opens it, key by key.
11
+ *
12
+ * Two keys, each opening one thing, and an absent key is the closed answer:
13
+ *
14
+ * - `exec.code.custody: attested` opens the class for the shell forms. It is the
15
+ * operator's statement that credential custody is in place: opaque code run by
16
+ * an agent that can read the tenant's credentials is a way round every gate
17
+ * that guards them, so the class may not exist without that statement.
18
+ * - `exec.code.execute_code: hooked`, beside the first, opens Hermes's
19
+ * `execute_code`. It is the operator's statement that a tool call made from
20
+ * inside the `execute_code` kernel re-fires the hook, which only a live probe
21
+ * can establish (`docs/hermes-hook.md`); without it one `execute_code` call
22
+ * would be an unbounded bypass of the adapter.
23
+ *
24
+ * Pure: a function of the parsed policy and nothing else. It decides whether a
25
+ * call may be CLASSIFIED `exec.code`; what the class then resolves to is the
26
+ * policy's ordinary class resolution (an `exec.code` row, an `exec.*` row, or
27
+ * `defaults.autonomy`), unchanged.
28
+ */
29
+ /** The class an opened program is judged under. Mirrors `command-class.ts`. */
30
+ export const EXEC_CODE_POLICY_CLASS = "exec.code";
31
+ /** The policy line that opens the class, as a refusal names it. */
32
+ export const EXEC_CODE_CUSTODY_KEY = "exec.code.custody: attested";
33
+ /** The policy line that opens Hermes `execute_code`, as a refusal names it. */
34
+ export const EXEC_CODE_EXECUTE_KEY = "exec.code.execute_code: hooked";
35
+ /** The gate with everything closed for `reason`. */
36
+ export function closedExecCodeGate(reason) {
37
+ return { shell: false, executeCode: false, closed: reason };
38
+ }
39
+ /**
40
+ * Does the policy name `exec.code` anywhere? An `exec` block, or a `classes`
41
+ * key that is exactly `exec.code`. A family row (`exec.*`) is not a mention:
42
+ * it was written for `exec.local`, and a policy that predates the class keeps
43
+ * its refusals byte for byte.
44
+ */
45
+ export function execCodeMentioned(policy) {
46
+ if (policy.exec !== undefined)
47
+ return true;
48
+ return Object.prototype.hasOwnProperty.call(policy.classes ?? {}, EXEC_CODE_POLICY_CLASS);
49
+ }
50
+ /** What `policy` opens. `null` (no policy loaded) opens nothing. */
51
+ export function execCodeGate(policy) {
52
+ if (policy === null)
53
+ return closedExecCodeGate("silent");
54
+ const code = policy.exec?.code;
55
+ if (code?.custody !== "attested") {
56
+ return closedExecCodeGate(execCodeMentioned(policy) ? "unattested" : "silent");
57
+ }
58
+ return { shell: true, executeCode: code.execute_code === "hooked", closed: null };
59
+ }
60
+ //# sourceMappingURL=exec-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-code.js","sourceRoot":"","sources":["../../../src/core/exec-code.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAYH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,sBAAsB,GAAG,WAAW,CAAC;AAElD,mEAAmE;AACnE,MAAM,CAAC,MAAM,qBAAqB,GAAG,6BAA6B,CAAC;AAEnE,+EAA+E;AAC/E,MAAM,CAAC,MAAM,qBAAqB,GAAG,gCAAgC,CAAC;AA4BtE,oDAAoD;AACpD,MAAM,UAAU,kBAAkB,CAAC,MAAsB;IACvD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,sBAAsB,CAAC,CAAC;AAC5F,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC;IAC/B,IAAI,IAAI,EAAE,OAAO,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,kBAAkB,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,YAAY,KAAK,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACpF,CAAC"}
@@ -1096,6 +1096,33 @@ export type RequestResult = {
1096
1096
  * may have changed.
1097
1097
  */
1098
1098
  export declare function request(logPath: string, input: RequestInput, actor: string, options?: GateOptions): RequestResult;
1099
+ /**
1100
+ * Why a study denied a request (amended SPEC.md §8), the closed vocabulary of
1101
+ * `payload.study.reason` on `approval.rejected`.
1102
+ *
1103
+ * - `study_withdrawn`: a hosted operator withdrew a still-pending request
1104
+ * before it could run. Written by the operator's withdraw verb alone, under
1105
+ * `human:study-team` on channel `study`; a channel may never pass it.
1106
+ * - `looks_wrong`: the resident flagged the card as a suspected study card and
1107
+ * skipped it. An ordinary resident deny with the flag recorded.
1108
+ */
1109
+ export declare const STUDY_REASONS: readonly ["study_withdrawn", "looks_wrong"];
1110
+ export type StudyReason = (typeof STUDY_REASONS)[number];
1111
+ /**
1112
+ * The study record on a reject (amended SPEC.md §8), written as
1113
+ * `payload.study` on `approval.rejected` only.
1114
+ *
1115
+ * `operator` is an 8-hex pseudonymous id the operator's control plane derives
1116
+ * from the staff email under its own key (never the email), and `ref` is the withdraw's replay reference; both belong to
1117
+ * `study_withdrawn` and the schema refuses them on `looks_wrong`. A record of
1118
+ * why a person denied, never an input: no enforcement path reads it, and a
1119
+ * reader that ignores it reads an ordinary rejection.
1120
+ */
1121
+ export interface DecisionStudy {
1122
+ reason: StudyReason;
1123
+ operator?: string;
1124
+ ref?: string;
1125
+ }
1099
1126
  export interface DecideOptions extends GateOptions {
1100
1127
  /** Free-text note recorded in the event payload (SPEC.md §8's example). */
1101
1128
  note?: string;
@@ -1188,6 +1215,17 @@ export interface DecideOptions extends GateOptions {
1188
1215
  * here or anywhere reads it back.
1189
1216
  */
1190
1217
  rendering?: PromptRendering;
1218
+ /**
1219
+ * Why a study denied this request (amended SPEC.md §8), recorded as
1220
+ * `payload.study` on `approval.rejected` only.
1221
+ *
1222
+ * Written under the reject's own branch, the way `reaction` sits under the
1223
+ * grant's, so a value passed with `grant` or `revoke` is structurally unable
1224
+ * to reach a record. Copied field by field in a fixed shape, so nothing else
1225
+ * a caller hangs on the object reaches the log. A decision surface supplies
1226
+ * it; there is no CLI flag for it on `approval reject`.
1227
+ */
1228
+ study?: DecisionStudy;
1191
1229
  }
1192
1230
  export type DecideResult = {
1193
1231
  ok: true;
@@ -1901,6 +1901,20 @@ function attemptRequest(logPath, input, actor, options) {
1901
1901
  ...(live === null ? {} : { live }),
1902
1902
  };
1903
1903
  }
1904
+ // ---------------------------------------------------------------------------
1905
+ // decide
1906
+ // ---------------------------------------------------------------------------
1907
+ /**
1908
+ * Why a study denied a request (amended SPEC.md §8), the closed vocabulary of
1909
+ * `payload.study.reason` on `approval.rejected`.
1910
+ *
1911
+ * - `study_withdrawn`: a hosted operator withdrew a still-pending request
1912
+ * before it could run. Written by the operator's withdraw verb alone, under
1913
+ * `human:study-team` on channel `study`; a channel may never pass it.
1914
+ * - `looks_wrong`: the resident flagged the card as a suspected study card and
1915
+ * skipped it. An ordinary resident deny with the flag recorded.
1916
+ */
1917
+ export const STUDY_REASONS = ["study_withdrawn", "looks_wrong"];
1904
1918
  /**
1905
1919
  * The two grades a grant may not carry without the approver's own words
1906
1920
  * (amended SPEC.md §5.2). The same pair `core/audit.ts` enforces on a review,
@@ -2169,6 +2183,18 @@ function attemptDecide(logPath, actionKey, decision, actor, options) {
2169
2183
  ...(options.rendering.say_does_ignored === true ? { say_does_ignored: true } : {}),
2170
2184
  };
2171
2185
  }
2186
+ // Amended SPEC.md §8 (the study record). Reject only: a study may DENY a
2187
+ // pending request and never grant, alter or reopen one, so the field exists
2188
+ // on the one decision that denies. Copied field by field, `operator` and
2189
+ // `ref` only when present; the schema then holds each reason to its shape at
2190
+ // the write boundary.
2191
+ if (decision === "reject" && options.study !== undefined) {
2192
+ payload["study"] = {
2193
+ reason: options.study.reason,
2194
+ ...(options.study.operator === undefined ? {} : { operator: options.study.operator }),
2195
+ ...(options.study.ref === undefined ? {} : { ref: options.study.ref }),
2196
+ };
2197
+ }
2172
2198
  if (decision === "grant") {
2173
2199
  const cls = derivation.declared.class ?? "";
2174
2200
  const resolution = resolve(load, cls, derivation.declared.reversible === null ? {} : { reversible: derivation.declared.reversible });