@ashlr/hub 3.2.7 → 3.3.2

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 (192) hide show
  1. package/CHANGELOG.md +314 -2
  2. package/README.md +54 -38
  3. package/dist/build-identity.json +2 -2
  4. package/dist/cli/audit.js +18 -0
  5. package/dist/cli/audit.js.map +1 -1
  6. package/dist/cli/backlog.d.ts +0 -5
  7. package/dist/cli/backlog.js +18 -0
  8. package/dist/cli/backlog.js.map +1 -1
  9. package/dist/cli/comms.js +20 -0
  10. package/dist/cli/comms.js.map +1 -1
  11. package/dist/cli/completions.d.ts +6 -1
  12. package/dist/cli/completions.js +33 -10
  13. package/dist/cli/completions.js.map +1 -1
  14. package/dist/cli/daemon.js +10 -3
  15. package/dist/cli/daemon.js.map +1 -1
  16. package/dist/cli/dashboard.js +5 -4
  17. package/dist/cli/dashboard.js.map +1 -1
  18. package/dist/cli/demo.d.ts +0 -27
  19. package/dist/cli/demo.js +17 -0
  20. package/dist/cli/demo.js.map +1 -1
  21. package/dist/cli/help.js +22 -9
  22. package/dist/cli/help.js.map +1 -1
  23. package/dist/cli/inbox.d.ts +14 -2
  24. package/dist/cli/inbox.js +137 -23
  25. package/dist/cli/inbox.js.map +1 -1
  26. package/dist/cli/onboard.d.ts +0 -13
  27. package/dist/cli/onboard.js +20 -0
  28. package/dist/cli/onboard.js.map +1 -1
  29. package/dist/cli/resources.js +15 -0
  30. package/dist/cli/resources.js.map +1 -1
  31. package/dist/cli/run.js +1 -1
  32. package/dist/cli/run.js.map +1 -1
  33. package/dist/cli/sandbox.js +43 -0
  34. package/dist/cli/sandbox.js.map +1 -1
  35. package/dist/cli/serve.js +7 -3
  36. package/dist/cli/serve.js.map +1 -1
  37. package/dist/cli/setup.d.ts +5 -3
  38. package/dist/cli/setup.js +14 -7
  39. package/dist/cli/setup.js.map +1 -1
  40. package/dist/cli/tui.d.ts +0 -6
  41. package/dist/cli/tui.js +14 -0
  42. package/dist/cli/tui.js.map +1 -1
  43. package/dist/cli/update.js +3 -1
  44. package/dist/cli/update.js.map +1 -1
  45. package/dist/core/config.js +70 -0
  46. package/dist/core/config.js.map +1 -1
  47. package/dist/core/daemon/loop.js +83 -3
  48. package/dist/core/daemon/loop.js.map +1 -1
  49. package/dist/core/daemon/runtime-activation-resident-start-protocol.d.ts +141 -0
  50. package/dist/core/daemon/runtime-activation-resident-start-protocol.js +462 -0
  51. package/dist/core/daemon/runtime-activation-resident-start-protocol.js.map +1 -0
  52. package/dist/core/daemon/runtime-activation-resident-start-runtime.d.ts +27 -0
  53. package/dist/core/daemon/runtime-activation-resident-start-runtime.js +28 -0
  54. package/dist/core/daemon/runtime-activation-resident-start-runtime.js.map +1 -0
  55. package/dist/core/daemon/runtime-activation-stopped-consumer.d.ts +139 -0
  56. package/dist/core/daemon/runtime-activation-stopped-consumer.js +995 -0
  57. package/dist/core/daemon/runtime-activation-stopped-consumer.js.map +1 -0
  58. package/dist/core/daemon/runtime-activation-stopped-runtime.d.ts +36 -0
  59. package/dist/core/daemon/runtime-activation-stopped-runtime.js +134 -0
  60. package/dist/core/daemon/runtime-activation-stopped-runtime.js.map +1 -0
  61. package/dist/core/daemon/runtime-activation-transaction.d.ts +41 -17
  62. package/dist/core/daemon/runtime-activation-transaction.js +80 -11
  63. package/dist/core/daemon/runtime-activation-transaction.js.map +1 -1
  64. package/dist/core/daemon/runtime-release-dependency-inventory.js +1 -0
  65. package/dist/core/daemon/runtime-release-dependency-inventory.js.map +1 -1
  66. package/dist/core/daemon/service-install-authority.d.ts +1 -1
  67. package/dist/core/daemon/service-install-authority.js +4 -2
  68. package/dist/core/daemon/service-install-authority.js.map +1 -1
  69. package/dist/core/dashboard.d.ts +10 -0
  70. package/dist/core/dashboard.js +103 -28
  71. package/dist/core/dashboard.js.map +1 -1
  72. package/dist/core/effective-config.js +7 -0
  73. package/dist/core/effective-config.js.map +1 -1
  74. package/dist/core/fleet/automerge-pass.js +57 -73
  75. package/dist/core/fleet/automerge-pass.js.map +1 -1
  76. package/dist/core/fleet/decisions-ledger.js +36 -13
  77. package/dist/core/fleet/decisions-ledger.js.map +1 -1
  78. package/dist/core/fleet/external-skill-git-capture.js +9 -1
  79. package/dist/core/fleet/external-skill-git-capture.js.map +1 -1
  80. package/dist/core/fleet/judge-decision-metadata.d.ts +8 -1
  81. package/dist/core/fleet/judge-decision-metadata.js +14 -1
  82. package/dist/core/fleet/judge-decision-metadata.js.map +1 -1
  83. package/dist/core/fleet/judge-trace.js +2 -1
  84. package/dist/core/fleet/judge-trace.js.map +1 -1
  85. package/dist/core/fleet/manager.d.ts +21 -1
  86. package/dist/core/fleet/manager.js +137 -41
  87. package/dist/core/fleet/manager.js.map +1 -1
  88. package/dist/core/fleet/post-merge-credit-label.d.ts +10 -0
  89. package/dist/core/fleet/post-merge-credit-label.js +13 -0
  90. package/dist/core/fleet/post-merge-credit-label.js.map +1 -0
  91. package/dist/core/fleet/post-merge-credit.d.ts +119 -12
  92. package/dist/core/fleet/post-merge-credit.js +353 -14
  93. package/dist/core/fleet/post-merge-credit.js.map +1 -1
  94. package/dist/core/fleet/self-improve.d.ts +38 -5
  95. package/dist/core/fleet/self-improve.js +201 -12
  96. package/dist/core/fleet/self-improve.js.map +1 -1
  97. package/dist/core/fleet/skill-library.d.ts +21 -2
  98. package/dist/core/fleet/skill-library.js +54 -12
  99. package/dist/core/fleet/skill-library.js.map +1 -1
  100. package/dist/core/fleet/worked-ledger.d.ts +22 -0
  101. package/dist/core/fleet/worked-ledger.js +32 -0
  102. package/dist/core/fleet/worked-ledger.js.map +1 -1
  103. package/dist/core/goals/store.js +60 -6
  104. package/dist/core/goals/store.js.map +1 -1
  105. package/dist/core/inbox/apply.js +2 -1
  106. package/dist/core/inbox/apply.js.map +1 -1
  107. package/dist/core/inbox/merge.d.ts +10 -1
  108. package/dist/core/inbox/merge.js +97 -18
  109. package/dist/core/inbox/merge.js.map +1 -1
  110. package/dist/core/integrations/github.d.ts +2 -0
  111. package/dist/core/integrations/github.js +2 -1
  112. package/dist/core/integrations/github.js.map +1 -1
  113. package/dist/core/learn/reflect.d.ts +42 -4
  114. package/dist/core/learn/reflect.js +66 -5
  115. package/dist/core/learn/reflect.js.map +1 -1
  116. package/dist/core/onboard.d.ts +2 -0
  117. package/dist/core/onboard.js +9 -3
  118. package/dist/core/onboard.js.map +1 -1
  119. package/dist/core/run/engines.d.ts +8 -0
  120. package/dist/core/run/engines.js +9 -1
  121. package/dist/core/run/engines.js.map +1 -1
  122. package/dist/core/run/learned-router.js +28 -3
  123. package/dist/core/run/learned-router.js.map +1 -1
  124. package/dist/core/run/orchestrator.d.ts +7 -2
  125. package/dist/core/run/orchestrator.js +88 -60
  126. package/dist/core/run/orchestrator.js.map +1 -1
  127. package/dist/core/swarm/rollback.d.ts +11 -0
  128. package/dist/core/swarm/rollback.js +54 -0
  129. package/dist/core/swarm/rollback.js.map +1 -1
  130. package/dist/core/swarm/runner.js +94 -8
  131. package/dist/core/swarm/runner.js.map +1 -1
  132. package/dist/core/types.d.ts +103 -3
  133. package/dist/core/types.js.map +1 -1
  134. package/dist/core/web/api.d.ts +6 -0
  135. package/dist/core/web/api.js +174 -18
  136. package/dist/core/web/api.js.map +1 -1
  137. package/dist/core/web/control.d.ts +20 -0
  138. package/dist/core/web/control.js +44 -32
  139. package/dist/core/web/control.js.map +1 -1
  140. package/dist/core/web/fleet-status-cache.d.ts +68 -0
  141. package/dist/core/web/fleet-status-cache.js +120 -0
  142. package/dist/core/web/fleet-status-cache.js.map +1 -0
  143. package/dist/core/web/public/app.js +5 -1
  144. package/dist/core/web/public/next/assets/index-CDHQraxS.css +1 -0
  145. package/dist/core/web/public/next/assets/index-zVKD9krw.js +69 -0
  146. package/dist/core/web/public/next/assets/index-zVKD9krw.js.map +1 -0
  147. package/dist/core/web/public/next/index.html +15 -0
  148. package/dist/core/web/read-session-revocations.d.ts +26 -0
  149. package/dist/core/web/read-session-revocations.js +48 -0
  150. package/dist/core/web/read-session-revocations.js.map +1 -0
  151. package/dist/core/web/server.js +73 -27
  152. package/dist/core/web/server.js.map +1 -1
  153. package/dist/core/web/static.d.ts +1 -1
  154. package/dist/core/web/static.js +54 -20
  155. package/dist/core/web/static.js.map +1 -1
  156. package/dist/release-dependency-inventory.json +1 -1
  157. package/docs/RUNTIME_ACTIVATION_AUTHORITY.md +115 -21
  158. package/docs/contracts/CONTRACT-M521.md +102 -0
  159. package/node_modules/fast-uri/index.js +201 -48
  160. package/node_modules/fast-uri/lib/schemes.js +9 -4
  161. package/node_modules/fast-uri/lib/utils.js +388 -91
  162. package/node_modules/fast-uri/package.json +1 -1
  163. package/node_modules/fast-uri/test/component-safe-serialization.test.js +105 -0
  164. package/node_modules/fast-uri/test/equal.test.js +31 -3
  165. package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
  166. package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
  167. package/node_modules/fast-uri/test/ipv6-validation.test.js +90 -0
  168. package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
  169. package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
  170. package/node_modules/fast-uri/test/parse.test.js +7 -3
  171. package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
  172. package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
  173. package/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
  174. package/node_modules/fast-uri/test/security-normalization.test.js +101 -0
  175. package/node_modules/fast-uri/test/security.test.js +75 -3
  176. package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
  177. package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
  178. package/node_modules/qs/.github/THREAT_MODEL.md +3 -3
  179. package/node_modules/qs/CHANGELOG.md +26 -0
  180. package/node_modules/qs/README.md +19 -1
  181. package/node_modules/qs/dist/qs.js +25 -25
  182. package/node_modules/qs/eslint.config.mjs +1 -1
  183. package/node_modules/qs/lib/parse.js +16 -6
  184. package/node_modules/qs/lib/stringify.js +23 -8
  185. package/node_modules/qs/lib/utils.js +57 -11
  186. package/node_modules/qs/package.json +8 -7
  187. package/node_modules/qs/test/parse.js +419 -0
  188. package/node_modules/qs/test/stringify.js +317 -0
  189. package/node_modules/qs/test/utils.js +206 -0
  190. package/package.json +24 -6
  191. package/schema/config.schema.json +800 -5
  192. package/node_modules/which/CHANGELOG.md +0 -166
@@ -1,18 +1,125 @@
1
1
  /**
2
- * Positive post-merge learning authority.
2
+ * Post-merge learning evidence — report-only release protocol.
3
3
  *
4
- * A realized merge is an immutable operational fact, not proof that the change
5
- * remained beneficial. Only the future purpose-built release protocol may mint
6
- * this label. Existing merge receipts and observation-only stability witnesses
7
- * deliberately do not satisfy it.
4
+ * A realized merge is an immutable operational fact, not proof that the
5
+ * change remained beneficial. This module is the only prospective release
6
+ * boundary, but it mints no operational authority while the observation source
7
+ * remains adverse-only:
8
+ *
9
+ * 1. The proposal must carry an AUTHENTICATED realized-merge witness
10
+ * (authenticatedRealizedMergeOf — binds to this exact proposal via a
11
+ * local receipt HMAC or host reconciliation; shape alone is never
12
+ * authority).
13
+ * 2. At least MIN_POST_MERGE_OBSERVATION_WINDOW_MS must have elapsed since
14
+ * the merge was observed. The regression sentinel / outcome watcher
15
+ * (outcome-watcher.ts) sweeps merged proposals on a 6h throttle with a
16
+ * 7-day follow-up window; 14 days gives ~2x that follow-up window and
17
+ * dozens of scan cycles of headroom before we trust the silence.
18
+ * 3. The signed, observation-only post-merge-observations ledger
19
+ * (post-merge-observations.ts) must contain NO adverse record
20
+ * ('regressed' | 'reverted' | 'followed-up') for this exact
21
+ * (repo, proposalId, mergeCommit) event, and the ledger must have read
22
+ * back healthy/complete (a degraded read fails closed — we never treat
23
+ * "couldn't check" as "checked clean").
24
+ *
25
+ * That ledger never records a positive "we checked, it's clean" row. Silence
26
+ * after a conservative window is diagnostic only, not positive confirmation;
27
+ * evaluation returns report-only and does not mint a label.
28
+ *
29
+ * HISTORICAL TOKEN SHAPE: the verifier remains fail-closed even for formerly
30
+ * valid HMAC-shaped values while operational release is disabled.
31
+ * isPostMergeCreditReleaseLabel() remains
32
+ * "structural recognition only" — matching the bare literal — for the
33
+ * existing defensive filters elsewhere that specifically reject the
34
+ * unauthenticated literal (e.g. skill-records.ts, decisions-ledger.ts).
8
35
  */
36
+ import type { Proposal, SkillCard } from '../types.js';
37
+ export { POST_MERGE_CREDIT_RELEASE_LABEL, isPostMergeCreditReleaseLabel, } from './post-merge-credit-label.js';
9
38
  export declare const POST_MERGE_CREDIT_POLICY_VERSION: "post-merge-credit-v1";
10
- export declare const POST_MERGE_CREDIT_RELEASE_LABEL: "post-merge-credit-release-v1";
11
- /** Structural recognition only; this string is not release authority. */
12
- export declare function isPostMergeCreditReleaseLabel(labelBasis: unknown): boolean;
13
39
  /**
14
- * No authenticated release verifier exists in this protocol version.
15
- * Deliberately return false even for the reserved label so raw ledgers,
16
- * injected dependencies, and caller-controlled metadata cannot mint credit.
40
+ * Minimum wall-clock time since the realized merge before credit MAY be
41
+ * released. See module doc for rationale (~2x the outcome-watcher follow-up
42
+ * window, dozens of throttled scan cycles of headroom).
43
+ */
44
+ export declare const MIN_POST_MERGE_OBSERVATION_WINDOW_MS: number;
45
+ /**
46
+ * Verify that `labelBasis` is a validly-signed post-merge-credit release
47
+ * token minted by evaluatePostMergeCreditRelease. Pure, synchronous
48
+ * (host-local key read only), never throws. Deliberately rejects the bare
49
+ * POST_MERGE_CREDIT_RELEASE_LABEL literal and any other string that is not a
50
+ * correctly-formed, correctly-signed token — so raw ledgers, injected
51
+ * dependencies, and caller-controlled metadata cannot mint credit.
52
+ */
53
+ export declare function hasReleasedPostMergeCredit(labelBasis: unknown): boolean;
54
+ export type PostMergeCreditEligibilityReason = 'clean-window-elapsed' | 'no-authenticated-merge-witness' | 'invalid-merge-timestamp' | 'observation-window-not-elapsed' | 'observation-ledger-degraded' | 'adverse-outcome-observed' | 'report-only-no-positive-stability-witness' | 'signing-unavailable' | 'error';
55
+ export interface PostMergeCreditEligibility {
56
+ eligible: boolean;
57
+ /** Signed token to persist as labelBasis when eligible; null otherwise. */
58
+ label: string | null;
59
+ reason: PostMergeCreditEligibilityReason;
60
+ }
61
+ /**
62
+ * Determine whether `proposal` has reportable post-merge evidence RIGHT NOW.
63
+ * Operational eligibility remains false until a positive stability witness
64
+ * exists. Fail-closed on every
65
+ * ambiguous path (missing witness, unparseable timestamp, window not yet
66
+ * elapsed, degraded/incomplete observation-ledger read, any adverse
67
+ * observation for this exact merge event, signing failure). Never throws.
68
+ *
69
+ * This function does NOT persist anything — see sweepPostMergeCreditReleases
70
+ * for the write-back that records a minted label to the decisions ledger.
71
+ */
72
+ export declare function evaluatePostMergeCreditRelease(proposal: Proposal, opts?: {
73
+ nowMs?: number;
74
+ }): PostMergeCreditEligibility;
75
+ export interface PostMergeCreditSweepResult {
76
+ /** Applied proposals inspected this pass. */
77
+ scanned: number;
78
+ /** Proposals for which a new credit-release decision row was recorded. */
79
+ released: number;
80
+ /** Proposals inspected but not released this pass (not yet eligible, or a
81
+ * transient error) — never a reason to trust the negative permanently. */
82
+ skipped: number;
83
+ /** False when the proposal or decisions source was incomplete/degraded, or
84
+ * the candidate cap was hit — a hint the pass should be retried. */
85
+ sourceComplete: boolean;
86
+ }
87
+ /**
88
+ * Sweep applied proposals for post-merge diagnostics. No operational release
89
+ * row is recorded while positive stability witnesses are unavailable.
90
+ *
91
+ * The post-merge-observations ledger only records adverse outcomes. Closing
92
+ * the release path requires a positive, complete "swept clean" witness bound
93
+ * to the exact merge event; elapsed time and silence are insufficient.
94
+ *
95
+ * SCALING NOTE: unlike outcome-watcher.ts, this sweep has no persistent
96
+ * cursor — it re-scans applied proposals from the top of listProposalsDetailed
97
+ * every pass, bounded by MAX_SWEEP_CANDIDATES. At small-to-moderate applied-
98
+ * proposal counts this converges fine (already-released proposals are
99
+ * skipped instantly via the decisions-ledger dedup check, so the cap mostly
100
+ * matters for proposals still awaiting their window). At very large scale a
101
+ * persistent cursor (mirroring monitoring-cursor.ts) would be needed to
102
+ * guarantee every proposal eventually gets swept; out of scope here.
103
+ *
104
+ * Never throws.
105
+ */
106
+ export declare function sweepPostMergeCreditReleases(opts?: {
107
+ nowMs?: number;
108
+ listProposals?: () => Proposal[];
109
+ }): PostMergeCreditSweepResult;
110
+ /**
111
+ * Sign a SkillCard that legitimately carries released post-merge credit.
112
+ *
113
+ * skill-attestation.ts's generic attestSkillCard() deliberately refuses any
114
+ * card for which hasReleasedPostMergeCredit(card.labelBasis) is true — its
115
+ * signature payload doesn't cover the credit-release claim, so it must not
116
+ * be the thing vouching for it. This module owns that proof, so it owns this
117
+ * signing boundary too: same crypto primitive (signSkillCardAttestation),
118
+ * same payload shape, so verifyAttestedSkillCard() on the read side still
119
+ * validates cards signed here — only the gating differs (this function signs
120
+ * ONLY when hasReleasedPostMergeCredit is true; attestSkillCard signs only
121
+ * when it is false).
122
+ *
123
+ * Never throws.
17
124
  */
18
- export declare function hasReleasedPostMergeCredit(_labelBasis: unknown): boolean;
125
+ export declare function attestPostMergeCreditSkillCard(card: SkillCard): SkillCard | null;
@@ -1,23 +1,362 @@
1
1
  /**
2
- * Positive post-merge learning authority.
2
+ * Post-merge learning evidence — report-only release protocol.
3
3
  *
4
- * A realized merge is an immutable operational fact, not proof that the change
5
- * remained beneficial. Only the future purpose-built release protocol may mint
6
- * this label. Existing merge receipts and observation-only stability witnesses
7
- * deliberately do not satisfy it.
4
+ * A realized merge is an immutable operational fact, not proof that the
5
+ * change remained beneficial. This module is the only prospective release
6
+ * boundary, but it mints no operational authority while the observation source
7
+ * remains adverse-only:
8
+ *
9
+ * 1. The proposal must carry an AUTHENTICATED realized-merge witness
10
+ * (authenticatedRealizedMergeOf — binds to this exact proposal via a
11
+ * local receipt HMAC or host reconciliation; shape alone is never
12
+ * authority).
13
+ * 2. At least MIN_POST_MERGE_OBSERVATION_WINDOW_MS must have elapsed since
14
+ * the merge was observed. The regression sentinel / outcome watcher
15
+ * (outcome-watcher.ts) sweeps merged proposals on a 6h throttle with a
16
+ * 7-day follow-up window; 14 days gives ~2x that follow-up window and
17
+ * dozens of scan cycles of headroom before we trust the silence.
18
+ * 3. The signed, observation-only post-merge-observations ledger
19
+ * (post-merge-observations.ts) must contain NO adverse record
20
+ * ('regressed' | 'reverted' | 'followed-up') for this exact
21
+ * (repo, proposalId, mergeCommit) event, and the ledger must have read
22
+ * back healthy/complete (a degraded read fails closed — we never treat
23
+ * "couldn't check" as "checked clean").
24
+ *
25
+ * That ledger never records a positive "we checked, it's clean" row. Silence
26
+ * after a conservative window is diagnostic only, not positive confirmation;
27
+ * evaluation returns report-only and does not mint a label.
28
+ *
29
+ * HISTORICAL TOKEN SHAPE: the verifier remains fail-closed even for formerly
30
+ * valid HMAC-shaped values while operational release is disabled.
31
+ * isPostMergeCreditReleaseLabel() remains
32
+ * "structural recognition only" — matching the bare literal — for the
33
+ * existing defensive filters elsewhere that specifically reject the
34
+ * unauthenticated literal (e.g. skill-records.ts, decisions-ledger.ts).
8
35
  */
36
+ import { createHmac, timingSafeEqual } from 'node:crypto';
37
+ import { resolve } from 'node:path';
38
+ import { loadOrCreateKey, signSkillCardAttestation } from '../foundry/provenance.js';
39
+ import { authenticatedRealizedMergeOf } from '../inbox/realized-merge.js';
40
+ import { listProposalsDetailed } from '../inbox/store.js';
41
+ import { causalMetadataFromProposal } from '../learning/causal.js';
42
+ import { readDecisions, recordDecision } from './decisions-ledger.js';
43
+ import { postMergeObservationEventId, readPostMergeObservations } from './post-merge-observations.js';
44
+ import { skillCardContentHash } from './skill-attestation.js';
45
+ import { POST_MERGE_CREDIT_RELEASE_LABEL } from './post-merge-credit-label.js';
46
+ export { POST_MERGE_CREDIT_RELEASE_LABEL, isPostMergeCreditReleaseLabel, } from './post-merge-credit-label.js';
47
+ // ---------------------------------------------------------------------------
48
+ // Structural constants
49
+ // ---------------------------------------------------------------------------
9
50
  export const POST_MERGE_CREDIT_POLICY_VERSION = 'post-merge-credit-v1';
10
- export const POST_MERGE_CREDIT_RELEASE_LABEL = 'post-merge-credit-release-v1';
11
- /** Structural recognition only; this string is not release authority. */
12
- export function isPostMergeCreditReleaseLabel(labelBasis) {
13
- return labelBasis === POST_MERGE_CREDIT_RELEASE_LABEL;
51
+ /**
52
+ * Operational release remains disabled until a positive, complete stability
53
+ * witness exists. The current adverse-only ledger supports reporting, not
54
+ * authority to steer routing or promote reusable skills.
55
+ */
56
+ const POST_MERGE_CREDIT_OPERATIONAL_RELEASE = false;
57
+ /**
58
+ * Minimum wall-clock time since the realized merge before credit MAY be
59
+ * released. See module doc for rationale (~2x the outcome-watcher follow-up
60
+ * window, dozens of throttled scan cycles of headroom).
61
+ */
62
+ export const MIN_POST_MERGE_OBSERVATION_WINDOW_MS = 14 * 24 * 60 * 60 * 1000;
63
+ /** Domain-separated HMAC payload prefix for minted credit tokens. */
64
+ const CREDIT_TOKEN_DOMAIN = 'ashlr:post-merge-credit-release:v1';
65
+ /**
66
+ * Both segments are truncated to 24 hex chars (96 bits each) — plenty for
67
+ * MAC-forgery infeasibility and event-identifier collision-resistance at
68
+ * fleet scale, and DELIBERATELY short for two independent reasons discovered
69
+ * empirically (see test/post-merge-credit-release.test.ts):
70
+ * 1. decisions-ledger.ts's generic secret-scrubber (util/scrub.ts) redacts
71
+ * any bare 64+ char hex run as a likely API key/hash — a 64-char segment
72
+ * would be silently corrupted the moment it passed through
73
+ * recordDecision().
74
+ * 2. recordDecision() -> sanitizeDecisionEntry() unconditionally ends with
75
+ * normalizeDecisionLearningFields() (learning/causal.ts), which re-runs
76
+ * causalMetadata() and applies boundedText(labelBasis, 80) — ANY
77
+ * labelBasis over 80 chars is silently truncated, with no way to opt
78
+ * out from a caller outside that module. The full token
79
+ * ("post-merge-credit-release-v1:" (30 chars) + eventRef + ':' + mac)
80
+ * must fit in 80 chars total, so each hex segment is capped at 24
81
+ * (30 + 24 + 1 + 24 = 79).
82
+ * Neither constraint is owned by this module; 24 hex chars satisfies both
83
+ * with margin.
84
+ */
85
+ const CREDIT_REF_HEX_CHARS = 24;
86
+ /** `post-merge-credit-release-v1:<event-ref-hex32>:<hmac-hex32>` */
87
+ const CREDIT_TOKEN_RE = new RegExp(`^post-merge-credit-release-v1:([a-f0-9]{${CREDIT_REF_HEX_CHARS}}):([a-f0-9]{${CREDIT_REF_HEX_CHARS}})$`);
88
+ /** Truncate a full sha256 eventId to the reference used inside the token. */
89
+ function creditEventRef(eventId) {
90
+ return eventId.slice(0, CREDIT_REF_HEX_CHARS);
91
+ }
92
+ function computeCreditMac(eventRef) {
93
+ try {
94
+ return createHmac('sha256', loadOrCreateKey())
95
+ .update(`${CREDIT_TOKEN_DOMAIN}:${eventRef}`)
96
+ .digest('hex')
97
+ .slice(0, CREDIT_REF_HEX_CHARS);
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ }
103
+ function equalHex(left, right) {
104
+ try {
105
+ const a = Buffer.from(left, 'hex');
106
+ const b = Buffer.from(right, 'hex');
107
+ return a.length === b.length && a.length > 0 && timingSafeEqual(a, b);
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
113
+ /**
114
+ * Verify that `labelBasis` is a validly-signed post-merge-credit release
115
+ * token minted by evaluatePostMergeCreditRelease. Pure, synchronous
116
+ * (host-local key read only), never throws. Deliberately rejects the bare
117
+ * POST_MERGE_CREDIT_RELEASE_LABEL literal and any other string that is not a
118
+ * correctly-formed, correctly-signed token — so raw ledgers, injected
119
+ * dependencies, and caller-controlled metadata cannot mint credit.
120
+ */
121
+ export function hasReleasedPostMergeCredit(labelBasis) {
122
+ try {
123
+ if (!POST_MERGE_CREDIT_OPERATIONAL_RELEASE)
124
+ return false;
125
+ if (typeof labelBasis !== 'string')
126
+ return false;
127
+ const match = CREDIT_TOKEN_RE.exec(labelBasis);
128
+ if (!match)
129
+ return false;
130
+ const eventRef = match[1];
131
+ const mac = match[2];
132
+ const expected = computeCreditMac(eventRef);
133
+ if (!expected)
134
+ return false;
135
+ return equalHex(mac, expected);
136
+ }
137
+ catch {
138
+ return false;
139
+ }
14
140
  }
15
141
  /**
16
- * No authenticated release verifier exists in this protocol version.
17
- * Deliberately return false even for the reserved label so raw ledgers,
18
- * injected dependencies, and caller-controlled metadata cannot mint credit.
142
+ * Determine whether `proposal` has reportable post-merge evidence RIGHT NOW.
143
+ * Operational eligibility remains false until a positive stability witness
144
+ * exists. Fail-closed on every
145
+ * ambiguous path (missing witness, unparseable timestamp, window not yet
146
+ * elapsed, degraded/incomplete observation-ledger read, any adverse
147
+ * observation for this exact merge event, signing failure). Never throws.
148
+ *
149
+ * This function does NOT persist anything — see sweepPostMergeCreditReleases
150
+ * for the write-back that records a minted label to the decisions ledger.
151
+ */
152
+ export function evaluatePostMergeCreditRelease(proposal, opts = {}) {
153
+ try {
154
+ if (!proposal || typeof proposal.id !== 'string' || proposal.id.length === 0) {
155
+ return { eligible: false, label: null, reason: 'no-authenticated-merge-witness' };
156
+ }
157
+ const evidence = authenticatedRealizedMergeOf(proposal);
158
+ if (!evidence || typeof proposal.repo !== 'string' || proposal.repo.length === 0) {
159
+ return { eligible: false, label: null, reason: 'no-authenticated-merge-witness' };
160
+ }
161
+ const observedAtIso = evidence.source === 'github-host'
162
+ ? evidence.reconciliation.observedAt
163
+ : evidence.observedAt;
164
+ const mergedAtMs = Date.parse(observedAtIso);
165
+ if (!Number.isFinite(mergedAtMs)) {
166
+ return { eligible: false, label: null, reason: 'invalid-merge-timestamp' };
167
+ }
168
+ const nowMs = opts.nowMs ?? Date.now();
169
+ if (nowMs < mergedAtMs || nowMs - mergedAtMs < MIN_POST_MERGE_OBSERVATION_WINDOW_MS) {
170
+ return { eligible: false, label: null, reason: 'observation-window-not-elapsed' };
171
+ }
172
+ let repoAbs;
173
+ try {
174
+ repoAbs = resolve(proposal.repo);
175
+ }
176
+ catch {
177
+ return { eligible: false, label: null, reason: 'no-authenticated-merge-witness' };
178
+ }
179
+ const eventId = postMergeObservationEventId({
180
+ repo: repoAbs,
181
+ proposalId: proposal.id,
182
+ mergeCommit: evidence.mergeCommitOid.toLowerCase(),
183
+ });
184
+ // Adverse-only ledger: absence of a row is the only "clean" signal it can
185
+ // ever give us. A degraded/incomplete read must NOT be treated as clean.
186
+ const read = readPostMergeObservations({ requireComplete: true });
187
+ if (read.sourceState === 'degraded' || !read.complete) {
188
+ return { eligible: false, label: null, reason: 'observation-ledger-degraded' };
189
+ }
190
+ if (read.observations.some((observation) => observation.eventId === eventId)) {
191
+ return { eligible: false, label: null, reason: 'adverse-outcome-observed' };
192
+ }
193
+ // Silence in an adverse-only ledger is useful diagnostic evidence, but it
194
+ // is not positive proof that this exact merge was observed stable. Keep
195
+ // the entire release path report-only until such a witness exists.
196
+ if (!POST_MERGE_CREDIT_OPERATIONAL_RELEASE) {
197
+ return {
198
+ eligible: false,
199
+ label: null,
200
+ reason: 'report-only-no-positive-stability-witness',
201
+ };
202
+ }
203
+ const eventRef = creditEventRef(eventId);
204
+ const mac = computeCreditMac(eventRef);
205
+ if (!mac)
206
+ return { eligible: false, label: null, reason: 'signing-unavailable' };
207
+ return {
208
+ eligible: true,
209
+ label: `${POST_MERGE_CREDIT_RELEASE_LABEL}:${eventRef}:${mac}`,
210
+ reason: 'clean-window-elapsed',
211
+ };
212
+ }
213
+ catch {
214
+ return { eligible: false, label: null, reason: 'error' };
215
+ }
216
+ }
217
+ // ---------------------------------------------------------------------------
218
+ // sweepPostMergeCreditReleases — periodic write-back
219
+ // ---------------------------------------------------------------------------
220
+ /**
221
+ * Bound on candidates inspected per sweep pass — mirrors outcome-watcher.ts's
222
+ * own bounding so a large backlog degrades gracefully instead of blocking.
223
+ */
224
+ const MAX_SWEEP_CANDIDATES = 200;
225
+ /**
226
+ * Sweep applied proposals for post-merge diagnostics. No operational release
227
+ * row is recorded while positive stability witnesses are unavailable.
228
+ *
229
+ * The post-merge-observations ledger only records adverse outcomes. Closing
230
+ * the release path requires a positive, complete "swept clean" witness bound
231
+ * to the exact merge event; elapsed time and silence are insufficient.
232
+ *
233
+ * SCALING NOTE: unlike outcome-watcher.ts, this sweep has no persistent
234
+ * cursor — it re-scans applied proposals from the top of listProposalsDetailed
235
+ * every pass, bounded by MAX_SWEEP_CANDIDATES. At small-to-moderate applied-
236
+ * proposal counts this converges fine (already-released proposals are
237
+ * skipped instantly via the decisions-ledger dedup check, so the cap mostly
238
+ * matters for proposals still awaiting their window). At very large scale a
239
+ * persistent cursor (mirroring monitoring-cursor.ts) would be needed to
240
+ * guarantee every proposal eventually gets swept; out of scope here.
241
+ *
242
+ * Never throws.
243
+ */
244
+ export function sweepPostMergeCreditReleases(opts = {}) {
245
+ const result = {
246
+ scanned: 0, released: 0, skipped: 0, sourceComplete: true,
247
+ };
248
+ try {
249
+ const nowMs = opts.nowMs ?? Date.now();
250
+ let proposals;
251
+ if (typeof opts.listProposals === 'function') {
252
+ try {
253
+ proposals = opts.listProposals();
254
+ }
255
+ catch {
256
+ proposals = [];
257
+ }
258
+ }
259
+ else {
260
+ const read = listProposalsDetailed({ status: 'applied', requireComplete: true });
261
+ if (!read.complete || read.sourceState === 'degraded') {
262
+ result.sourceComplete = false;
263
+ return result;
264
+ }
265
+ proposals = read.proposals;
266
+ }
267
+ let inspected = 0;
268
+ for (const proposal of proposals) {
269
+ if (proposal.status !== 'applied')
270
+ continue;
271
+ if (inspected >= MAX_SWEEP_CANDIDATES) {
272
+ result.sourceComplete = false;
273
+ break;
274
+ }
275
+ inspected++;
276
+ result.scanned++;
277
+ try {
278
+ const existing = readDecisions({ proposalId: proposal.id, requireComplete: true });
279
+ const existingQuality = existing.sourceQuality;
280
+ if (existingQuality && (existingQuality.sourceState === 'degraded' || existingQuality.complete === false)) {
281
+ result.skipped++;
282
+ result.sourceComplete = false;
283
+ continue;
284
+ }
285
+ const alreadyReleased = existing.some((entry) => entry.action === 'merged' && hasReleasedPostMergeCredit(entry.labelBasis));
286
+ if (alreadyReleased)
287
+ continue;
288
+ const eligibility = evaluatePostMergeCreditRelease(proposal, { nowMs });
289
+ if (!eligibility.eligible || !eligibility.label) {
290
+ result.skipped++;
291
+ continue;
292
+ }
293
+ const ts = new Date(nowMs).toISOString();
294
+ recordDecision({
295
+ ts,
296
+ proposalId: proposal.id,
297
+ // NOTE: labelBasis is intentionally NOT routed through
298
+ // causalMetadataFromProposal's `input.labelBasis` — causalMetadata()
299
+ // (learning/causal.ts) applies boundedText(value, 80) to that field,
300
+ // which would silently truncate our signed token (>80 chars) into
301
+ // an unverifiable fragment. Set it directly below instead, after the
302
+ // spread, so the full token survives.
303
+ ...causalMetadataFromProposal(proposal, { ts, learningSource: 'outcome-record' }),
304
+ action: 'merged',
305
+ verdict: 'applied',
306
+ labelBasis: eligibility.label,
307
+ repo: proposal.repo ?? '',
308
+ engine: proposal.engineModel ?? '',
309
+ model: '',
310
+ });
311
+ result.released++;
312
+ }
313
+ catch {
314
+ result.skipped++;
315
+ }
316
+ }
317
+ return result;
318
+ }
319
+ catch {
320
+ return result;
321
+ }
322
+ }
323
+ // ---------------------------------------------------------------------------
324
+ // attestPostMergeCreditSkillCard — dedicated signing boundary
325
+ // ---------------------------------------------------------------------------
326
+ /**
327
+ * Sign a SkillCard that legitimately carries released post-merge credit.
328
+ *
329
+ * skill-attestation.ts's generic attestSkillCard() deliberately refuses any
330
+ * card for which hasReleasedPostMergeCredit(card.labelBasis) is true — its
331
+ * signature payload doesn't cover the credit-release claim, so it must not
332
+ * be the thing vouching for it. This module owns that proof, so it owns this
333
+ * signing boundary too: same crypto primitive (signSkillCardAttestation),
334
+ * same payload shape, so verifyAttestedSkillCard() on the read side still
335
+ * validates cards signed here — only the gating differs (this function signs
336
+ * ONLY when hasReleasedPostMergeCredit is true; attestSkillCard signs only
337
+ * when it is false).
338
+ *
339
+ * Never throws.
19
340
  */
20
- export function hasReleasedPostMergeCredit(_labelBasis) {
21
- return false;
341
+ export function attestPostMergeCreditSkillCard(card) {
342
+ try {
343
+ if (!hasReleasedPostMergeCredit(card.labelBasis))
344
+ return null;
345
+ const diffHash = card.verification?.diffHash;
346
+ if (!card.proposalId || !diffHash)
347
+ return null;
348
+ const contentHash = skillCardContentHash(card);
349
+ const attestation = signSkillCardAttestation({
350
+ contentHash,
351
+ skillId: card.skillId,
352
+ revision: card.revision,
353
+ proposalId: card.proposalId,
354
+ diffHash,
355
+ });
356
+ return attestation ? { ...card, contentHash, attestation } : null;
357
+ }
358
+ catch {
359
+ return null;
360
+ }
22
361
  }
23
362
  //# sourceMappingURL=post-merge-credit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"post-merge-credit.js","sourceRoot":"","sources":["../../../src/core/fleet/post-merge-credit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,sBAA+B,CAAC;AAChF,MAAM,CAAC,MAAM,+BAA+B,GAAG,8BAAuC,CAAC;AAEvF,yEAAyE;AACzE,MAAM,UAAU,6BAA6B,CAAC,UAAmB;IAC/D,OAAO,UAAU,KAAK,+BAA+B,CAAC;AACxD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,WAAoB;IAC7D,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"post-merge-credit.js","sourceRoot":"","sources":["../../../src/core/fleet/post-merge-credit.ts"],"names":[],"mappings":"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"}
@@ -10,8 +10,8 @@
10
10
  * policy file.
11
11
  * - FIRE-AND-FORGET: learnFromRejection() never throws, never awaits
12
12
  * anything on the critical path. All I/O is wrapped in try/catch.
13
- * - GATED: every code path checks cfg.foundry?.selfImprove !== false
14
- * (default ON). When the flag is explicitly false the function returns
13
+ * - GATED: every code path checks cfg.foundry?.selfImprove === true
14
+ * (default OFF). Unless the flag is explicitly true the function returns
15
15
  * immediately — byte-identical to having no call at all.
16
16
  * - ADDITIVE: the genome entry is informational grounding for future runs.
17
17
  * It is NOT an execution directive; it does not alter the merge gate,
@@ -23,7 +23,7 @@
23
23
  * - TELEMETRY: a usage counter is appended to the decisions ledger under
24
24
  * action 'self-improve:written' for observability (no PII, no secrets).
25
25
  */
26
- import type { AshlrConfig, GenomeEntry } from '../types.js';
26
+ import type { AshlrConfig, DecisionEntry, GenomeEntry } from '../types.js';
27
27
  /** Hard cap on total characters injected from anti-playbook entries per run. */
28
28
  export declare const ANTI_PLAYBOOK_INJECT_CAP = 800;
29
29
  type RejectionVerdict = 'review' | 'noise' | 'harmful';
@@ -42,7 +42,7 @@ export declare function deriveLesson(verdict: RejectionVerdict, _reasoning: stri
42
42
  * 2. A decisions-ledger entry for telemetry/observability.
43
43
  *
44
44
  * NEVER THROWS. NEVER BLOCKS (all I/O is synchronous JSONL append behind
45
- * try/catch). Gated on cfg.foundry?.selfImprove !== false.
45
+ * try/catch). Gated on cfg.foundry?.selfImprove === true.
46
46
  *
47
47
  * @param proposalId The proposal id (for ledger correlation).
48
48
  * @param proposalTitle Legacy compatibility input; deliberately discarded.
@@ -50,7 +50,40 @@ export declare function deriveLesson(verdict: RejectionVerdict, _reasoning: stri
50
50
  * @param reasoning Legacy compatibility input; deliberately discarded.
51
51
  * @param cfg Fleet config.
52
52
  */
53
- export declare function learnFromRejection(proposalId: string, proposalTitle: string, verdict: string, reasoning: string, cfg: AshlrConfig): void;
53
+ export declare function learnFromRejection(proposalId: string, proposalTitle: string, verdict: string, reasoning: string, cfg: AshlrConfig): boolean;
54
+ export interface RejectionLearningSweepResult {
55
+ /** Distinct proposals with a rejection-shaped judge verdict inspected. */
56
+ scanned: number;
57
+ /** Proposals for which learnFromRejection actually wrote a lesson. */
58
+ written: number;
59
+ /** Proposals inspected but skipped (already written, or a transient error). */
60
+ skipped: number;
61
+ /** False when the decisions source was incomplete/degraded, or the
62
+ * candidate cap was hit — a hint the pass should be retried. */
63
+ sourceComplete: boolean;
64
+ }
65
+ /**
66
+ * Sweep the decisions ledger for considered judged rejection verdicts (review
67
+ * | noise | harmful with their matching judgeReasonCode) and call
68
+ * learnFromRejection() for each one that hasn't already produced a lesson.
69
+ *
70
+ * learnFromRejection's ONLY prior caller was automerge-pass.ts, reached
71
+ * exclusively from inside the auto-merge pass (cfg.foundry.autoMerge.enabled
72
+ * === true). With auto-merge off — the default — a judge verdict could still
73
+ * be recorded (manual `ashlr judge`, a shadow/observe-only judge pass, etc.)
74
+ * but the fleet never learned from it. This sweep reads the SAME
75
+ * decisions-ledger 'judged' rows regardless of what produced them, so a
76
+ * rejected or failed run teaches the fleet whether or not auto-merge is on.
77
+ *
78
+ * Idempotent: a proposalId that already has a 'self-improve:written'
79
+ * telemetry row is skipped. Bounded, fail-open-on-nothing-to-do,
80
+ * fail-closed-on-source-degradation (sourceComplete:false signals "retry
81
+ * later", never treated as "nothing to learn"). Never throws.
82
+ */
83
+ export declare function sweepRejectionLearning(cfg: AshlrConfig, opts?: {
84
+ sinceMs?: number;
85
+ listDecisions?: () => DecisionEntry[];
86
+ }): RejectionLearningSweepResult;
54
87
  /**
55
88
  * Filter a list of genome entries to anti-playbook entries suitable for
56
89
  * inject-time grounding. Applies: