@groundnuty/macf 0.2.36 → 0.2.38

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 (121) hide show
  1. package/dist/.build-info.json +2 -2
  2. package/dist/cli/claude-sh.d.ts +12 -10
  3. package/dist/cli/claude-sh.d.ts.map +1 -1
  4. package/dist/cli/claude-sh.js +13 -11
  5. package/dist/cli/claude-sh.js.map +1 -1
  6. package/dist/cli/commands/certs.d.ts.map +1 -1
  7. package/dist/cli/commands/certs.js +6 -2
  8. package/dist/cli/commands/certs.js.map +1 -1
  9. package/dist/cli/commands/doctor.d.ts +102 -3
  10. package/dist/cli/commands/doctor.d.ts.map +1 -1
  11. package/dist/cli/commands/doctor.js +349 -55
  12. package/dist/cli/commands/doctor.js.map +1 -1
  13. package/dist/cli/commands/init.d.ts +24 -0
  14. package/dist/cli/commands/init.d.ts.map +1 -1
  15. package/dist/cli/commands/init.js +81 -8
  16. package/dist/cli/commands/init.js.map +1 -1
  17. package/dist/cli/commands/monitor.d.ts +16 -0
  18. package/dist/cli/commands/monitor.d.ts.map +1 -0
  19. package/dist/cli/commands/monitor.js +96 -0
  20. package/dist/cli/commands/monitor.js.map +1 -0
  21. package/dist/cli/commands/propose.d.ts +21 -0
  22. package/dist/cli/commands/propose.d.ts.map +1 -0
  23. package/dist/cli/commands/propose.js +128 -0
  24. package/dist/cli/commands/propose.js.map +1 -0
  25. package/dist/cli/commands/ps.d.ts +17 -0
  26. package/dist/cli/commands/ps.d.ts.map +1 -0
  27. package/dist/cli/commands/ps.js +69 -0
  28. package/dist/cli/commands/ps.js.map +1 -0
  29. package/dist/cli/commands/registry-prune.d.ts +44 -0
  30. package/dist/cli/commands/registry-prune.d.ts.map +1 -0
  31. package/dist/cli/commands/registry-prune.js +124 -0
  32. package/dist/cli/commands/registry-prune.js.map +1 -0
  33. package/dist/cli/commands/rules-refresh.d.ts +1 -0
  34. package/dist/cli/commands/rules-refresh.d.ts.map +1 -1
  35. package/dist/cli/commands/rules-refresh.js +22 -1
  36. package/dist/cli/commands/rules-refresh.js.map +1 -1
  37. package/dist/cli/commands/update.d.ts.map +1 -1
  38. package/dist/cli/commands/update.js +23 -2
  39. package/dist/cli/commands/update.js.map +1 -1
  40. package/dist/cli/config.d.ts +2 -0
  41. package/dist/cli/config.d.ts.map +1 -1
  42. package/dist/cli/config.js +16 -0
  43. package/dist/cli/config.js.map +1 -1
  44. package/dist/cli/env-files-update.d.ts.map +1 -1
  45. package/dist/cli/env-files-update.js +5 -1
  46. package/dist/cli/env-files-update.js.map +1 -1
  47. package/dist/cli/env-files.d.ts +38 -13
  48. package/dist/cli/env-files.d.ts.map +1 -1
  49. package/dist/cli/env-files.js +84 -14
  50. package/dist/cli/env-files.js.map +1 -1
  51. package/dist/cli/index.js +142 -5
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/cli/monitor/digest.d.ts +89 -0
  54. package/dist/cli/monitor/digest.d.ts.map +1 -0
  55. package/dist/cli/monitor/digest.js +232 -0
  56. package/dist/cli/monitor/digest.js.map +1 -0
  57. package/dist/cli/monitor/github-reader.d.ts +38 -0
  58. package/dist/cli/monitor/github-reader.d.ts.map +1 -0
  59. package/dist/cli/monitor/github-reader.js +65 -0
  60. package/dist/cli/monitor/github-reader.js.map +1 -0
  61. package/dist/cli/monitor/reflections.d.ts +18 -0
  62. package/dist/cli/monitor/reflections.d.ts.map +1 -0
  63. package/dist/cli/monitor/reflections.js +72 -0
  64. package/dist/cli/monitor/reflections.js.map +1 -0
  65. package/dist/cli/monitor/run.d.ts +30 -0
  66. package/dist/cli/monitor/run.d.ts.map +1 -0
  67. package/dist/cli/monitor/run.js +67 -0
  68. package/dist/cli/monitor/run.js.map +1 -0
  69. package/dist/cli/proc-scan.d.ts +81 -0
  70. package/dist/cli/proc-scan.d.ts.map +1 -0
  71. package/dist/cli/proc-scan.js +172 -0
  72. package/dist/cli/proc-scan.js.map +1 -0
  73. package/dist/cli/project-rules.d.ts +105 -0
  74. package/dist/cli/project-rules.d.ts.map +1 -0
  75. package/dist/cli/project-rules.js +305 -0
  76. package/dist/cli/project-rules.js.map +1 -0
  77. package/dist/cli/propose/candidates.d.ts +95 -0
  78. package/dist/cli/propose/candidates.d.ts.map +1 -0
  79. package/dist/cli/propose/candidates.js +117 -0
  80. package/dist/cli/propose/candidates.js.map +1 -0
  81. package/dist/cli/propose/invariants.d.ts +49 -0
  82. package/dist/cli/propose/invariants.d.ts.map +1 -0
  83. package/dist/cli/propose/invariants.js +154 -0
  84. package/dist/cli/propose/invariants.js.map +1 -0
  85. package/dist/cli/propose/proposal-writer.d.ts +33 -0
  86. package/dist/cli/propose/proposal-writer.d.ts.map +1 -0
  87. package/dist/cli/propose/proposal-writer.js +53 -0
  88. package/dist/cli/propose/proposal-writer.js.map +1 -0
  89. package/dist/cli/propose/report.d.ts +49 -0
  90. package/dist/cli/propose/report.d.ts.map +1 -0
  91. package/dist/cli/propose/report.js +227 -0
  92. package/dist/cli/propose/report.js.map +1 -0
  93. package/dist/cli/propose/run.d.ts +41 -0
  94. package/dist/cli/propose/run.d.ts.map +1 -0
  95. package/dist/cli/propose/run.js +62 -0
  96. package/dist/cli/propose/run.js.map +1 -0
  97. package/dist/cli/role-settings-model.d.ts +70 -0
  98. package/dist/cli/role-settings-model.d.ts.map +1 -0
  99. package/dist/cli/role-settings-model.js +90 -0
  100. package/dist/cli/role-settings-model.js.map +1 -0
  101. package/dist/cli/settings-writer.d.ts +103 -6
  102. package/dist/cli/settings-writer.d.ts.map +1 -1
  103. package/dist/cli/settings-writer.js +259 -8
  104. package/dist/cli/settings-writer.js.map +1 -1
  105. package/dist/reconciler/reconcile.d.ts +31 -0
  106. package/dist/reconciler/reconcile.d.ts.map +1 -1
  107. package/dist/reconciler/reconcile.js +47 -3
  108. package/dist/reconciler/reconcile.js.map +1 -1
  109. package/dist/reconciler/run.d.ts +21 -1
  110. package/dist/reconciler/run.d.ts.map +1 -1
  111. package/dist/reconciler/run.js +106 -17
  112. package/dist/reconciler/run.js.map +1 -1
  113. package/package.json +2 -2
  114. package/plugin/rules/gh-token-attribution-traps.md +4 -0
  115. package/plugin/rules/observability-wiring.md +3 -3
  116. package/plugin/rules/reflection-staging.md +65 -0
  117. package/plugin/rules/silent-fallback-hazards.md +21 -4
  118. package/scripts/check-auditor-never-acts.sh +167 -0
  119. package/scripts/check-gh-attribution.sh +254 -0
  120. package/scripts/emit-turn-receipt.sh +1 -1
  121. package/scripts/harvest-reflection.sh +125 -0
@@ -38,6 +38,36 @@
38
38
  export function receiptKey(p) {
39
39
  return `${p.runId}:${p.agent}`;
40
40
  }
41
+ /**
42
+ * macf#479 coalesced-turn gate. For a would-be drop, find a *different*
43
+ * delivery to the same agent within ±proximityMs that WAS receipted. Its
44
+ * existence means the agent was alive + processing routed turns around the
45
+ * delivery, so this one benignly coalesced/clobbered into a sibling's turn.
46
+ *
47
+ * Crucially it counts only ROUTED receipts (the `processed` set), so an
48
+ * RC-bound agent — alive on the RC-SDK but silently dropping ALL its tmux
49
+ * pings, none of which receipt — has NO receipted sibling near a real drop and
50
+ * is correctly NOT suppressed. (Returns null ⇒ no benign sibling ⇒ real drop.)
51
+ */
52
+ function findReceiptedSibling(route, inScope, processedKeys, proximityMs) {
53
+ let best = null;
54
+ for (const sib of inScope) {
55
+ if (sib.runId === route.runId)
56
+ continue; // not itself
57
+ if (sib.agent !== route.agent)
58
+ continue; // same agent only
59
+ const deltaMs = sib.deliveredAtMs - route.deliveredAtMs;
60
+ if (Math.abs(deltaMs) > proximityMs)
61
+ continue; // outside the window
62
+ if (!processedKeys.has(receiptKey(sib)))
63
+ continue; // sibling must be receipted
64
+ // Prefer the temporally-nearest receipted sibling for the log.
65
+ if (best === null || Math.abs(deltaMs) < Math.abs(best.deltaMs)) {
66
+ best = { route, siblingRunId: sib.runId, deltaMs };
67
+ }
68
+ }
69
+ return best;
70
+ }
41
71
  /**
42
72
  * Pure reconciliation: which delivered routes are drops (no receipt span +
43
73
  * older than the open-threshold) vs. still-in-flight (no receipt yet but
@@ -52,22 +82,36 @@ export function reconcile(delivered, processed, opts) {
52
82
  // every pre-deployment route in the lookback window.
53
83
  const sinceMs = opts.sinceMs;
54
84
  const inScope = sinceMs ? delivered.filter((r) => r.deliveredAtMs >= sinceMs) : delivered;
85
+ const proximityMs = opts.proximityMs ?? 0;
55
86
  const drops = [];
56
87
  const inFlight = [];
88
+ const suppressed = [];
57
89
  for (const route of inScope) {
58
90
  if (processedKeys.has(receiptKey(route)))
59
91
  continue; // receipt landed — fine
60
92
  const ageMs = opts.nowMs - route.deliveredAtMs;
61
- if (ageMs > opts.openThresholdMs) {
62
- drops.push(route); // unmatched + past the threshold structural drop
93
+ if (ageMs <= opts.openThresholdMs) {
94
+ inFlight.push(route); // unmatched but young busy agent may process late
95
+ continue;
96
+ }
97
+ // Unmatched + past the threshold → a would-be drop. macf#479: suppress it
98
+ // (benign coalesce) iff a receipted sibling delivery to the same agent sits
99
+ // within the proximity window — the agent was demonstrably processing routed
100
+ // turns then. No receipted sibling (lone / offline / RC-bound) ⇒ real drop.
101
+ const sibling = proximityMs > 0
102
+ ? findReceiptedSibling(route, inScope, processedKeys, proximityMs)
103
+ : null;
104
+ if (sibling !== null) {
105
+ suppressed.push(sibling);
63
106
  }
64
107
  else {
65
- inFlight.push(route); // unmatched but young → busy agent may process late
108
+ drops.push(route);
66
109
  }
67
110
  }
68
111
  return {
69
112
  drops,
70
113
  inFlight,
114
+ suppressed,
71
115
  deliveredCount: inScope.length,
72
116
  processedCount: processedKeys.size,
73
117
  };
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../src/reconciler/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAyDH,sDAAsD;AACtD,MAAM,UAAU,UAAU,CAAC,CAAmC;IAC5D,OAAO,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CACvB,SAAoC,EACpC,SAAsC,EACtC,IAAsB;IAEtB,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IAEzD,yEAAyE;IACzE,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,qDAAqD;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC7B,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE1F,MAAM,KAAK,GAAqB,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAqB,EAAE,CAAC;IAEtC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAAE,SAAS,CAAC,wBAAwB;QAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC;QAC/C,IAAI,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,mDAAmD;QACxE,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,oDAAoD;QAC5E,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK;QACL,QAAQ;QACR,cAAc,EAAE,OAAO,CAAC,MAAM;QAC9B,cAAc,EAAE,aAAa,CAAC,IAAI;KACnC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../src/reconciler/reconcile.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,23 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ /** DELIVERED set: parse the router runs' `Routed … to <AGENT>` success lines.
3
+ * `truncated` is true when `gh run list` hit its page cap — the set is then
4
+ * UNKNOWABLE (older routes fell off the page), so the caller must NOT flag
5
+ * drops this run. Symmetric to the Tempo `tempo_ok` guard, on the delivered
6
+ * side (Pattern A): silently bounding coverage would let a real drop hide. */
7
+ /**
8
+ * Window-aware DELIVERED truncation (macf#477). The set is UNKNOWABLE only when
9
+ * the run-list page is full AND its OLDEST run is still inside the lookback
10
+ * window — i.e. older in-window runs were pushed off the page. When the oldest
11
+ * run on the page predates the window start, the window is fully covered and
12
+ * the page is NOT truncated (the older entries are simply out-of-window).
13
+ *
14
+ * The previous check (`runs.length >= LIMIT`) measured LIFETIME page-fullness,
15
+ * so it went dark — `delivered_ok=false` every run — in any repo with >LIMIT
16
+ * lifetime router runs regardless of in-window count, making the reconciler a
17
+ * permanent no-op (and blocking #462 self-close). `gh run list` returns
18
+ * newest-first, so `runs.at(-1)` is the oldest on the page.
19
+ */
20
+ export declare function isDeliveredTruncated(runs: ReadonlyArray<{
21
+ createdAt: string;
22
+ }>, nowMs: number, lookbackMs: number, limit: number): boolean;
3
23
  //# sourceMappingURL=run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/reconciler/run.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/reconciler/run.ts"],"names":[],"mappings":";AAkGA;;;;+EAI+E;AAC/E;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,aAAa,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1C,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAOT"}
@@ -27,15 +27,25 @@
27
27
  * Config (env, set by the workflow):
28
28
  * RECONCILER_REPO owner/repo (default $GITHUB_REPOSITORY)
29
29
  * ROUTER_WORKFLOW router workflow file (default agent-router.yml)
30
- * TEMPO_QUERY_ENDPOINT Tempo query base, e.g. http://<tailnet-host>:13200
30
+ * TEMPO_QUERY_ENDPOINT Tempo query base; default is the dedicated
31
+ * monitoring VM over Tailscale on the OTel-native
32
+ * port (no +10000 offset):
33
+ * http://orzech-dev-agents-monitoring.tail491af.ts.net:3200
34
+ * (macf#516 — the old k3d loopback default is DEAD)
31
35
  * OPEN_THRESHOLD_MIN drop threshold, must exceed busy-turn latency (default 15)
32
- * LOOKBACK_MIN how far back to scan runs + Tempo (default 120; keep
33
- * below the window where the router exceeds the run-list
34
- * cap, else the DELIVERED set truncates delivered_ok=false)
36
+ * PROXIMITY_MIN macf#479 coalesced-turn gate: suppress a would-be drop
37
+ * when a receipted sibling delivery to the same agent is
38
+ * within ±this (default 5; must be < OPEN_THRESHOLD_MIN)
39
+ * LOOKBACK_MIN how far back to scan runs + Tempo (default 120). The
40
+ * DELIVERED query is scoped to this window via
41
+ * `gh run list --created`, so truncation means
42
+ * ">RUN_LIST_LIMIT router runs INSIDE the window"
43
+ * (genuine high volume), not lifetime page-fullness (macf#477)
35
44
  * TEMPO_LIMIT Tempo search result cap (default 1000)
36
45
  */
37
46
  import { execFileSync } from 'node:child_process';
38
- import { appendFileSync } from 'node:fs';
47
+ import { appendFileSync, realpathSync } from 'node:fs';
48
+ import { fileURLToPath } from 'node:url';
39
49
  import { reconcile } from './reconcile.js';
40
50
  import { parseDeliveredFromLog } from './parse-delivered.js';
41
51
  import { parseProcessedFromTempo, receiptsIfComplete } from './parse-processed.js';
@@ -54,9 +64,14 @@ const envStr = (name, def) => {
54
64
  };
55
65
  const REPO = envStr('RECONCILER_REPO', process.env['GITHUB_REPOSITORY'] ?? '');
56
66
  const ROUTER_WORKFLOW = envStr('ROUTER_WORKFLOW', 'agent-router.yml');
57
- const TEMPO = envStr('TEMPO_QUERY_ENDPOINT', 'http://127.0.0.1:13200').replace(/\/+$/, '');
67
+ const TEMPO = envStr('TEMPO_QUERY_ENDPOINT', 'http://orzech-dev-agents-monitoring.tail491af.ts.net:3200').replace(/\/+$/, '');
58
68
  const OPEN_THRESHOLD_MS = Number(envStr('OPEN_THRESHOLD_MIN', '15')) * MIN;
59
69
  const LOOKBACK_MS = Number(envStr('LOOKBACK_MIN', '120')) * MIN;
70
+ // macf#479 coalesced-turn gate: suppress a would-be drop when a receipted
71
+ // sibling delivery to the same agent is within ±this. Must be > the turn-batching
72
+ // window (~couple min) and < OPEN_THRESHOLD_MIN (so real lone/offline/RC-bound
73
+ // drops still flag). 0 ⇒ disabled.
74
+ const PROXIMITY_MS = Number(envStr('PROXIMITY_MIN', '5')) * MIN;
60
75
  const TEMPO_LIMIT = Number(envStr('TEMPO_LIMIT', '1000'));
61
76
  /** Deployment-boundary cutoff (groundnuty/macf#444): ignore routes delivered
62
77
  * before the receipt mechanism went live. Set `RECONCILER_SINCE` to the
@@ -81,13 +96,43 @@ const SINCE_MS = (() => {
81
96
  * UNKNOWABLE (older routes fell off the page), so the caller must NOT flag
82
97
  * drops this run. Symmetric to the Tempo `tempo_ok` guard, on the delivered
83
98
  * side (Pattern A): silently bounding coverage would let a real drop hide. */
99
+ /**
100
+ * Window-aware DELIVERED truncation (macf#477). The set is UNKNOWABLE only when
101
+ * the run-list page is full AND its OLDEST run is still inside the lookback
102
+ * window — i.e. older in-window runs were pushed off the page. When the oldest
103
+ * run on the page predates the window start, the window is fully covered and
104
+ * the page is NOT truncated (the older entries are simply out-of-window).
105
+ *
106
+ * The previous check (`runs.length >= LIMIT`) measured LIFETIME page-fullness,
107
+ * so it went dark — `delivered_ok=false` every run — in any repo with >LIMIT
108
+ * lifetime router runs regardless of in-window count, making the reconciler a
109
+ * permanent no-op (and blocking #462 self-close). `gh run list` returns
110
+ * newest-first, so `runs.at(-1)` is the oldest on the page.
111
+ */
112
+ export function isDeliveredTruncated(runs, nowMs, lookbackMs, limit) {
113
+ if (runs.length < limit)
114
+ return false; // page not full → window fully covered
115
+ const oldest = runs[runs.length - 1];
116
+ if (oldest === undefined)
117
+ return false;
118
+ const oldestMs = Date.parse(oldest.createdAt);
119
+ if (!Number.isFinite(oldestMs))
120
+ return true; // unparseable oldest → conservative: unknowable
121
+ return oldestMs > nowMs - lookbackMs; // oldest still in-window ⇒ older runs fell off ⇒ truncated
122
+ }
84
123
  function fetchDelivered(nowMs) {
124
+ // Scope the query to the lookback window SERVER-SIDE (macf#477): the page then
125
+ // holds only in-window runs, so a full page genuinely means ">LIMIT deliveries
126
+ // INSIDE the window" — and we fetch logs only for in-window runs instead of all
127
+ // RUN_LIST_LIMIT (incl. out-of-window ones the old code fetched then discarded).
128
+ const windowStartIso = new Date(nowMs - LOOKBACK_MS).toISOString();
85
129
  const listJson = execFileSync('gh', ['run', 'list', '--repo', REPO, '--workflow', ROUTER_WORKFLOW,
86
- '--status', 'success', '--limit', String(RUN_LIST_LIMIT), '--json', 'databaseId,createdAt'], { encoding: 'utf-8' });
130
+ '--status', 'success', '--created', `>=${windowStartIso}`,
131
+ '--limit', String(RUN_LIST_LIMIT), '--json', 'databaseId,createdAt'], { encoding: 'utf-8' });
87
132
  const runs = JSON.parse(listJson);
88
- const truncated = runs.length >= RUN_LIST_LIMIT;
133
+ const truncated = isDeliveredTruncated(runs, nowMs, LOOKBACK_MS, RUN_LIST_LIMIT);
89
134
  if (truncated) {
90
- console.error(`WARN: gh run list hit the ${RUN_LIST_LIMIT}-run cap — DELIVERED set UNKNOWABLE (truncated; older routes in the ${LOOKBACK_MS / MIN}-min window fell off the page). Emitting delivered_ok=false (no drops this run); narrow LOOKBACK_MIN or raise the cap.`);
135
+ console.error(`WARN: >${RUN_LIST_LIMIT} router runs INSIDE the ${LOOKBACK_MS / MIN}-min window — DELIVERED set UNKNOWABLE (older in-window routes fell off the ${RUN_LIST_LIMIT}-run page). Emitting delivered_ok=false (no drops this run); narrow LOOKBACK_MIN or raise the cap.`);
91
136
  }
92
137
  const out = [];
93
138
  for (const run of runs) {
@@ -162,23 +207,67 @@ async function main() {
162
207
  emit({ tempoOk: true, deliveredOk: false, dropsCount: 0, inFlightCount: 0, dropsJson: '[]' });
163
208
  return;
164
209
  }
165
- const result = reconcile(delivered, processed, { nowMs, openThresholdMs: OPEN_THRESHOLD_MS, sinceMs: SINCE_MS });
210
+ const result = reconcile(delivered, processed, {
211
+ nowMs, openThresholdMs: OPEN_THRESHOLD_MS, sinceMs: SINCE_MS, proximityMs: PROXIMITY_MS,
212
+ });
166
213
  const dropsJson = JSON.stringify(result.drops);
167
214
  console.error(`reconcile: delivered=${result.deliveredCount} processed=${result.processedCount} ` +
168
- `drops=${result.drops.length} in_flight=${result.inFlight.length}` +
215
+ `drops=${result.drops.length} in_flight=${result.inFlight.length} suppressed=${result.suppressed.length}` +
169
216
  (SINCE_MS ? ` (since=${new Date(SINCE_MS).toISOString()} — pre-deployment routes excluded)` : ''));
217
+ // macf#479: coalesced-turn suppressions are LOUD + counted, NEVER silent. Each
218
+ // is a delivery whose marker never receipted but whose agent demonstrably
219
+ // processed a sibling ROUTED turn within PROXIMITY_MS (benign coalesce/clobber,
220
+ // not a real send≠receipt drop). Logging the rate keeps the underlying
221
+ // C-u-clobber visible (its source-side fix is tracked separately); a spike here
222
+ // is itself a signal. RC-bound real-drops are NOT suppressed (no receipted
223
+ // sibling — their tmux pings all drop), so the detector keeps its #437 cause-3
224
+ // teeth. Known residual (rare, same loud-log captures the rate): a content-loss
225
+ // clobber (the clobbering sibling DID receipt) is suppressed — and likewise a
226
+ // genuinely-independent transport loss of A that happens to fall within ±T of an
227
+ // UNRELATED receipted sibling. Both are closed at the root by the source-side
228
+ // C-u-clobber fix; until then the suppressed-rate signal keeps them visible.
229
+ for (const s of result.suppressed) {
230
+ console.error(`suppressed_coalesced_drop: run=${s.route.runId} agent=${s.route.agent} ` +
231
+ `sibling=${s.siblingRunId} delta_ms=${s.deltaMs} ` +
232
+ `(benign: receipted sibling within ${PROXIMITY_MS / MIN}min — macf#479)`);
233
+ }
170
234
  if (result.drops.length > 0)
171
235
  console.error(`DROPS: ${dropsJson}`);
172
- emit({ tempoOk: true, deliveredOk: true, dropsCount: result.drops.length, inFlightCount: result.inFlight.length, dropsJson });
236
+ emit({
237
+ tempoOk: true, deliveredOk: true, dropsCount: result.drops.length,
238
+ inFlightCount: result.inFlight.length, dropsJson, suppressedCount: result.suppressed.length,
239
+ });
173
240
  }
174
241
  function emit(o) {
175
242
  const out = process.env['GITHUB_OUTPUT'];
176
243
  if (!out)
177
244
  return;
178
- appendFileSync(out, `tempo_ok=${o.tempoOk}\ndelivered_ok=${o.deliveredOk}\ndrops_count=${o.dropsCount}\nin_flight_count=${o.inFlightCount}\ndrops_json=${o.dropsJson}\n`);
245
+ appendFileSync(out, `tempo_ok=${o.tempoOk}\ndelivered_ok=${o.deliveredOk}\ndrops_count=${o.dropsCount}\n` +
246
+ `in_flight_count=${o.inFlightCount}\nsuppressed_count=${o.suppressedCount ?? 0}\ndrops_json=${o.dropsJson}\n`);
247
+ }
248
+ /**
249
+ * Run main() ONLY when invoked as the CLI entrypoint (the route-reconciler.yml
250
+ * workflow runs `node dist/reconciler/run.js`). Guarded so the unit test can
251
+ * `import { isDeliveredTruncated }` from this module WITHOUT triggering main():
252
+ * an unguarded top-level main() rejects under the test's no-network env and
253
+ * calls process.exit(1), which vitest surfaces as an unhandled error + leaves a
254
+ * pending fetch keeping the worker alive (macf#477 test imported it; macf#479).
255
+ */
256
+ function isCliEntrypoint() {
257
+ const argv1 = process.argv[1];
258
+ if (argv1 === undefined)
259
+ return false;
260
+ try {
261
+ return realpathSync(argv1) === realpathSync(fileURLToPath(import.meta.url));
262
+ }
263
+ catch {
264
+ return false; // argv1 unresolvable (e.g. under a test runner) — not the entrypoint
265
+ }
266
+ }
267
+ if (isCliEntrypoint()) {
268
+ main().catch((err) => {
269
+ console.error(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
270
+ process.exit(1);
271
+ });
179
272
  }
180
- main().catch((err) => {
181
- console.error(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
182
- process.exit(1);
183
- });
184
273
  //# sourceMappingURL=run.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/reconciler/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/reconciler/run.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundnuty/macf",
3
- "version": "0.2.36",
3
+ "version": "0.2.38",
4
4
  "description": "Multi-Agent Coordination Framework CLI — coordinate Claude Code agents via GitHub. Installs as `macf` binary; use `macf init` to set up an agent workspace, `macf update` to refresh rules + version pins.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,7 +35,7 @@
35
35
  "test:watch": "vitest"
36
36
  },
37
37
  "dependencies": {
38
- "@groundnuty/macf-core": "0.2.36",
38
+ "@groundnuty/macf-core": "0.2.38",
39
39
  "commander": "^14.0.3",
40
40
  "reflect-metadata": "^0.2.2",
41
41
  "zod": "^4.0.0"
@@ -50,6 +50,10 @@ case "$TOKEN" in ghs_*) ;; *) echo "FATAL: bad token prefix" >&2; exit 1 ;; esac
50
50
  export GH_TOKEN="$TOKEN"
51
51
  ```
52
52
 
53
+ **Why the bare `TOKEN=$(...)` + separate `export`, never `export GH_TOKEN=$(...)`:** `export` is a builtin whose own exit status (`0`) *replaces* the command-substitution's, so `export GH_TOKEN=$(helper) || exit 1` and `export GH_TOKEN=$(helper) && gh ...` both proceed even when the helper failed (ShellCheck SC2155). The failure only propagates from a **bare** assignment (`TOKEN=$(helper) || …`). This is load-bearing under GitHub's intermittent token-endpoint 401s: a refresh that can yield an empty token *without aborting* the dependent `gh` ops re-creates mode #3 (witnessed 2026-06-12 — `cv-architect` posted 4 comments as the operator under a transient 401).
54
+
55
+ **The PreToolUse hook does NOT cover this.** `check-gh-token.sh` validates the *ambient* `GH_TOKEN` before the command runs, so an inline `export GH_TOKEN=$(...) && gh ...` (refresh-chain *or* file-cache read) reassigns the token *after* the hook has already passed — the hook is structurally blind to it (silent-fallback **Instance 12**, `silent-fallback-hazards.md`). Refresh in a **separate step** (to a pre-validated file, or a bare-assigned var with the checks above), never inline in the `gh` command. The durable structural cover is a result-invariant **PostToolUse** `author`-check (macf#489), not the PreToolUse precondition.
56
+
53
57
  ### 4. Wrong `gh auth` on the VM providing fallback
54
58
 
55
59
  Having `gh auth login` configured as a user account creates the fallback surface in #3. Even a "good" setup where the script is correct can hide a broken bot token because `gh` quietly uses the user auth.
@@ -13,7 +13,7 @@ Every MACF agent's `claude.sh` launcher exports the Claude Code native OpenTelem
13
13
  | `OTEL_TRACES_EXPORTER` | `otlp` | Emit traces. Without it, no spans even if master gate is on |
14
14
  | `OTEL_METRICS_EXPORTER` | `otlp` | Emit metrics. **Per-signal** — separate from traces |
15
15
  | `OTEL_LOGS_EXPORTER` | `otlp` | Emit logs. **Per-signal** — separate from traces |
16
- | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4318` (default) | OTLP HTTP receiver. Override via `MACF_OTEL_ENDPOINT` |
16
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:14318` (macf canonical — the bare OTLP default `:4318` is overridden by `macf init`/`update`) | OTLP HTTP receiver. Override via `MACF_OTEL_ENDPOINT` |
17
17
  | `OTEL_EXPORTER_OTLP_PROTOCOL` | `http/protobuf` | Wire protocol |
18
18
  | `OTEL_SERVICE_NAME` | `macf-agent-<name>` | All MACF agents grouped under one service.name family |
19
19
  | `OTEL_RESOURCE_ATTRIBUTES` | `gen_ai.agent.name=<name>,gen_ai.agent.role=<role>,service.namespace=macf` | Semconv-compliant resource attrs |
@@ -32,10 +32,10 @@ Per-workspace, two env knobs read at `macf init` / `macf update` time:
32
32
  - Deployment has no OTLP receiver running (no observability stack)
33
33
  - You want zero retry-spam from the exporter
34
34
  - Agent runs offline / on a host that can't reach the collector
35
- - `MACF_OTEL_ENDPOINT=http://central-host:4318` — overrides the default `http://localhost:4318`. Use when:
35
+ - `MACF_OTEL_ENDPOINT=http://central-host:14318` — overrides the default `http://localhost:14318`. Use when:
36
36
  - The observability stack is on a different host (Tailscale tailnet, reverse proxy, central collector)
37
37
  - You're running multiple MACF deployments and want them all reporting to one collector
38
- - The default port `4318` collides with another service on the agent's host (e.g., devops-toolkit's compose stack uses `:14318` for this reason)
38
+ - **Note:** the canonical macf-devops-toolkit k3d stack serves OTLP HTTP on `:14318` (the bare-SDK default `:4318` was retired 2026-04-25 after it collided with the prior compose stack and caused 34 min of zero-telemetry on CV agents — macf#282/#283). Use `:14318`, not `:4318`.
39
39
 
40
40
  To apply a knob: set the env var BEFORE running `macf update`. The launcher re-renders with the new state.
41
41
 
@@ -0,0 +1,65 @@
1
+ # Reflection Staging (canonical, shared)
2
+
3
+ **Maintain a staged reflection as you work. At compaction it is harvested automatically.** This file is the single source of truth for the reflection-staging discipline; it is copied into each agent workspace's `.claude/rules/` by `macf init` and refreshed by `macf update` / `macf rules refresh`. Do not edit workspace copies directly — edit the canonical file at `groundnuty/macf:packages/macf/plugin/rules/reflection-staging.md` and re-distribute.
4
+
5
+ Applies to every MACF agent. The mechanism is local + cheap and never blocks compaction (DR-026 F2, groundnuty/macf#500).
6
+
7
+ ---
8
+
9
+ ## What this is
10
+
11
+ You accumulate observations as you work — recurring patterns, canonical-rule breaches you committed or witnessed, signals that a rule should evolve, loose ends. Historically these lived in "remember to synthesize before compaction" / "codify at decision time" disciplines, which depended on you remembering to act at exactly the moment your context was about to be summarized away.
12
+
13
+ This rule gives those disciplines a **structural home**: a single file you keep current as you go. They stop being "remember to do X at compaction" and become "keep `pending.json` current." At compaction, the `harvest-reflection.sh` PreCompact hook reads the staged file, wraps it in the versioned reflection envelope, appends it to a local per-session JSONL ledger, and clears the stage for the next session. You never run the harvest by hand — the hook fires on both auto-compaction and manual `/compact`.
14
+
15
+ ---
16
+
17
+ ## The staged file
18
+
19
+ Maintain `.claude/.macf/reflections/pending.json` incrementally. It holds only the **protocol-signal fields** — the hook adds the envelope (`schema_version`, `kind`, agent identity, project, session id, timestamp, trigger, compaction type) at harvest time.
20
+
21
+ ```jsonc
22
+ {
23
+ // Recurring behaviour / coordination dynamic / workflow shape worth surfacing.
24
+ "observed_patterns": [
25
+ { "pattern": "<short name>", "evidence": "<what you saw / a ref>", "tier_hint": "<where it might belong>" }
26
+ ],
27
+ // Canonical-rule breaches you committed or witnessed this session.
28
+ "breaches": [
29
+ { "rule": "<rule name>", "what": "<the breach>", "ref": "<issue/PR/commit/file:line>" }
30
+ ],
31
+ // Signals that a coordination rule should evolve (proposals for governance).
32
+ "rule_evolution_signals": [
33
+ { "signal": "<what changed/recurred>", "proposed_tier": "<suggested tier>", "rationale": "<why>" }
34
+ ],
35
+ // Loose ends to pick up next session.
36
+ "unresolved": [ "<string>", "<string>" ],
37
+ // One-paragraph free-form synthesis of the session.
38
+ "synthesis": "<prose>"
39
+ }
40
+ ```
41
+
42
+ Every field is optional. An empty `{}` (the cleared state) is valid — the hook still emits a **mechanical-only** record at compaction so the Monitor sees that a compaction happened. Arrays default to `[]`, `synthesis` to `""`.
43
+
44
+ `observed_patterns[]` and `rule_evolution_signals[]` may carry an optional `key` field — reserved for deterministic cross-agent dedup later (additive; leave it out for now unless you have a stable key).
45
+
46
+ ## How to keep it current
47
+
48
+ - **Append, don't wait.** When you notice a pattern, breach, or evolution signal mid-session, add it to the relevant array right then. Don't batch it for "later" — later is compaction, and the point is that the staging already happened.
49
+ - **Edit the file directly** (`Read` then `Edit`/`Write`). It is plain JSON in your workspace; no command or tool call is needed.
50
+ - **Keep `synthesis` a living summary.** Overwrite it as your understanding of the session firms up.
51
+ - **Don't clear it yourself.** The harvest hook clears the stage after appending. If you clear it, the next compaction emits a mechanical-only record and your observations are lost.
52
+
53
+ ## Where it lands
54
+
55
+ - Ledger: `.claude/.macf/reflections/<session_id>.jsonl` (one JSON object per line, schema-validated by `@groundnuty/macf-core` `ReflectionRecordSchema`).
56
+ - The ledger is **local + observational** — DR-023 §UC-3 posture. The harvest never blocks compaction; an internal error fails open (`exit 0`).
57
+ - Opt out of harvesting for a session with `MACF_SKIP_REFLECTION_HARVEST=1`.
58
+
59
+ ---
60
+
61
+ ## When to read vs. modify
62
+
63
+ - **Read:** every session start. This rule defines how to stage your reflection.
64
+ - **Modify:** never directly in workspace copies. Edit the canonical file and re-distribute via `macf update`.
65
+ - **Disagree with a rule?** Open an issue on `groundnuty/macf` proposing the change, with rationale. Peer review applies.
@@ -4,7 +4,7 @@
4
4
 
5
5
  > **Workspaces without full `macf init`** (e.g. `groundnuty/macf` itself, or any Claude Code workspace operated by a bot that isn't a MACF-registered agent) can still get this canonical rule via `macf rules refresh --dir <workspace>`. Same copy, no App credentials or registry required.
6
6
 
7
- This rule names the CLASS so agents recognize the shape on first encounter rather than re-discovering each instance from scratch. Ten active instances are documented below as worked examples spanning different architectural layers (identity, parsing, TUI binding, observability routing, config substitution, multi-agent coordination protocol, metric-instrumentation lifecycle, observability-endpoint routing, release-pipeline-partial-publish, third-party-action retry-exhaustion). (Instance 10 — a legacy substrate-routing receipt-gap — was retired 2026-06-07; its number is kept, not reused.) Nine of ten active instances have structural defenses applied or in flight — the pattern of defense generalizes alongside the pattern of hazard.
7
+ This rule names the CLASS so agents recognize the shape on first encounter rather than re-discovering each instance from scratch. Eleven active instances are documented below as worked examples spanning different architectural layers (identity, parsing, TUI binding, observability routing, config substitution, multi-agent coordination protocol, metric-instrumentation lifecycle, observability-endpoint routing, release-pipeline-partial-publish, third-party-action retry-exhaustion, credential-refresh temporal-binding). (Instance 10 — a legacy substrate-routing receipt-gap — was retired 2026-06-07; its number is kept, not reused.) Ten of eleven active instances have structural defenses applied or in flight — the pattern of defense generalizes alongside the pattern of hazard.
8
8
 
9
9
  Instance 9 is annotated as **sister-shape** (failure correctly surfaced + partial side-effect breaks retry idempotency) — listed here for cross-reference convenience but warrants a sibling canonical rule (`partial-side-effect-hazards.md`) if more instances surface. The two classes share "multi-step pipeline where consumer assumes atomicity" but the failure surface differs: silent-fallback hides at the API boundary; partial-side-effect surfaces loudly but persists semi-state.
10
10
 
@@ -203,6 +203,22 @@ Generalizes to any retry-wrapping action: assert the result-invariant the connec
203
203
 
204
204
  ---
205
205
 
206
+ ### Instance 12 — PreToolUse credential-guard validates the *ambient* token, blind to inline mid-command reassignment
207
+
208
+ **Surface:** the #140 `check-gh-token.sh` PreToolUse hook, plus any refresh idiom that reassigns `GH_TOKEN` *inline within the same Bash command* as the `gh` calls it is meant to guard (`export GH_TOKEN=$(gh token generate ... | jq -r .token) && gh ...`; `export GH_TOKEN=$(cat tok.txt) && gh ...`).
209
+
210
+ **Failure shape:** the hook validates `GH_TOKEN` purely from the **ambient environment present *before* the command runs** (`GH_TOKEN_VALUE="${GH_TOKEN:-}"`, then the `^ghs_[A-Za-z0-9_]+$` predicate). It never parses the command string for an inline `GH_TOKEN=` / `export GH_TOKEN=$(...)` reassignment. Agents launch with a valid `ghs_` token in ambient env, so the hook **passes and exits 0** — *then* the inline `$(...)` runs, and on an intermittent GitHub-side 401 the naive `| jq` (no `set -o pipefail`) emits empty/`null`, clobbering `GH_TOKEN` to empty **after the hook has already returned**. The chained `gh` calls fall back to the stored `gh auth login` user. **The recommended refresh-chain bypasses its own guard.** The hook only blocks when the ambient is *already* bad — the exact case the rules tell agents not to rely on; in the normal regime (valid ambient) it is a pass-through no-op for every inline-refresh shape, including the file-cache read.
211
+
212
+ Two adjacent sub-failures: **(a)** `export X=$(helper)` masks a fail-loud helper's non-zero exit, because `export` is a builtin whose own exit `0` replaces the substitution's (ShellCheck SC2155) — so `pipefail` / a fail-loud helper *alone* is insufficient; only `GH_TOKEN=$(helper) || exit 1` (bare assignment + explicit abort) short-circuits the `&&`. **(b)** A redirect `helper > tok.txt` truncates the cache file *before* the helper runs, so a 401 leaves an *empty* file for the next read — write atomic-validated (`mktemp` + `grep ghs_` + `mv`) instead.
213
+
214
+ **Recurrence:** First confirmed — `macf-cv-architect` 2026-06-12 (4 issue/PR comments posted as the operator under an intermittent GitHub-side 401). Verified by 3-lens adversarial review (source-code / shell-mechanism / alternative-cause, 3-0 survived). Generalizes to every agent's inline / file-cache refresh form; the substrate workbenches additionally had the footgun *taught* by an unrefreshed bootstrap `gh-token-refresh.md` that `macf update` does not distribute.
215
+
216
+ **Defense status:** layered. **(1) DOC (shipped):** de-footgun `gh-token-refresh.md` (+ `agent-identity.md`) — fail-loud `GH_TOKEN=$(helper) || exit 1`, no inline-refresh, atomic-validated file-cache; this rule's sister `gh-token-attribution-traps.md` strengthened with the export-mask. **(2) STRUCTURAL — the load-bearing fix, Pattern A:** a result-invariant **PostToolUse** check asserting the just-written resource's `author` == expected bot login (`macf-whoami.sh` / `--json author`) — the only level that sees through inline-clobber, file-cache-staleness, *and* future bypass shapes, because it checks what was actually posted, not the command-string shape (filed macf#489). **(3) Decided-against:** teaching the PreToolUse hook to detect inline `GH_TOKEN=$(...)` reassignment — brittle regex over arbitrary shell, and the export-mask makes the safe-predicate subtle.
217
+
218
+ **Class lesson:** a structural defense that validates a precondition at the **wrong temporal level** — pre-command ambient state instead of the post-mutation runtime value — provides no protection in exactly the regime it was built for. **Result-invariant assertion at the boundary (Pattern A) is the temporal-level-agnostic fix; command-string precondition checks are not.** This is the clearest case yet of *why* Pattern A bears the most weight in this rule.
219
+
220
+ ---
221
+
206
222
  ## How to recognize the class on first encounter
207
223
 
208
224
  When investigating a "the operation completed but the outcome is wrong" incident, suspect silent-fallback if ANY of:
@@ -358,10 +374,11 @@ For coordination-system safety analysis: this is a class of hazards multi-agent
358
374
  | 8 — OTLP endpoint silent-drop | Observability-endpoint routing | Five-surface defense: CLI release-discipline + substrate testers env-override + canonical template `:14318` default + cluster-side compat port-map + agent-process `doctor-otel.sh` Pattern A | Pattern A (composite — first multi-architectural-layer case in this rule; instances 1-7 have single-pattern defenses) |
359
375
  | 9 — Sigstore TLOG orphans on failed npm publish (sister-class) | npm publish + sigstore attestation pipeline | Three-defense composite: bump-version recovery (DR-022 Amendment L) + pre-flight registry-collision check (Pattern D analog, macf#380) + TLOG-state observability (devops-toolkit#74+#77 Grafana dashboard live) | Pattern D analog (pre-flight precheck) + recovery-procedure-codification |
360
376
  | 11 — Third-party retry-wrapping action exits 0 on retry-exhaustion | Consumer-CI connect/auth via third-party action (tailnet, OTLP, cloud-auth, registry-login) | SHIPPED — "Verify <resource> is up" step immediately after the connect asserts the connection's result-invariant (e.g. `tailscale status` `BackendState == "Running"`) + fails LOUD; never trusts the action's exit code about its own retry exhaustion (macf#461) | Pattern A (post-connect result-invariant assert) + Pattern D flavor (precheck-before-downstream) |
377
+ | 12 — PreToolUse credential-guard validates ambient token, blind to inline reassignment | gh-token PreToolUse hook + inline `export GH_TOKEN=$(...) && gh` (refresh-chain or file-cache) | DOC shipped (de-footgun `gh-token-refresh.md` + atomic-validated cache) + STRUCTURAL in flight (Pattern A result-invariant PostToolUse whoami post-check, macf#489) | Pattern A (result-invariant post-check — a wrong-temporal-level precondition can't see the inline clobber) |
361
378
 
362
- Nine of ten active instances have structural defense applied or shipped. Defense patterns (A, B, C, D, E) generalize across instances — they're reusable defense templates, not case-specific fixes. **Pattern A (result-invariant assertion at the boundary) bears the most weight** — it's the structural defense for instances 4, 7, 8, AND 11 (4 of 10), each at a different architectural boundary (logs pipeline, metric counter, observability endpoint, third-party-action connect-verify). Instance 8's five-surface defense topology (consumer canonical + cluster-side compat port-map + concrete Pattern A impl) demonstrates that structural defense at the observability-pipeline-class can compose across architectural layers — the canonical-distribution layer + the cluster-infrastructure layer + the assertion-script layer all reinforce each other rather than substituting for each other. Instance 9 demonstrates that the Pattern D template generalizes from workflow-secrets-prechecks to release-pipeline-prechecks AND that recovery-procedure-codification (DR-022 Amendment L's bump-version-not-tag-retry) is its own defense category — distinct from detection-pre-merge defenses (Patterns A/B/D) and discrimination-at-receiver defenses (Pattern E).
379
+ Ten of eleven active instances have structural defense applied, shipped, or in flight. Defense patterns (A, B, C, D, E) generalize across instances — they're reusable defense templates, not case-specific fixes. **Pattern A (result-invariant assertion at the boundary) bears the most weight** — it's the structural defense for instances 4, 7, 8, 11, AND 12 (5 of 11), each at a different architectural boundary (logs pipeline, metric counter, observability endpoint, third-party-action connect-verify, credential-refresh temporal-binding). Instance 8's five-surface defense topology (consumer canonical + cluster-side compat port-map + concrete Pattern A impl) demonstrates that structural defense at the observability-pipeline-class can compose across architectural layers — the canonical-distribution layer + the cluster-infrastructure layer + the assertion-script layer all reinforce each other rather than substituting for each other. Instance 9 demonstrates that the Pattern D template generalizes from workflow-secrets-prechecks to release-pipeline-prechecks AND that recovery-procedure-codification (DR-022 Amendment L's bump-version-not-tag-retry) is its own defense category — distinct from detection-pre-merge defenses (Patterns A/B/D) and discrimination-at-receiver defenses (Pattern E).
363
380
 
364
- The breadth of layers spanned by 5 different defense patterns (identity, parsing, TUI binding, observability routing, config substitution, multi-agent coordination protocol, metric-instrumentation lifecycle, observability-endpoint routing, release-pipeline-partial-publish, third-party-action retry-exhaustion) is independent evidence that the hazard CLASS is real. If silent-fallback was a single-instance accident, no defense pattern would emerge. **Pattern A's recurrence across 3 different observability boundaries (logs / metrics / endpoint) is the strongest signal that result-invariant assertion is the load-bearing structural-defense template for the entire observability-pipeline-class** of silent fallback.
381
+ The breadth of layers spanned by 5 different defense patterns (identity, parsing, TUI binding, observability routing, config substitution, multi-agent coordination protocol, metric-instrumentation lifecycle, observability-endpoint routing, release-pipeline-partial-publish, third-party-action retry-exhaustion, credential-refresh temporal-binding) is independent evidence that the hazard CLASS is real. If silent-fallback was a single-instance accident, no defense pattern would emerge. **Pattern A's recurrence across 3 different observability boundaries (logs / metrics / endpoint) is the strongest signal that result-invariant assertion is the load-bearing structural-defense template for the entire observability-pipeline-class** of silent fallback.
365
382
 
366
383
  ---
367
384
 
@@ -375,7 +392,7 @@ Add when ALL of the following hold:
375
392
 
376
393
  The class-name is what makes the lesson transferable, not multi-agent witness. A single-agent-confirmed instance with a concrete trace + identified defense pattern is sufficient for canonicalization (instances 4, 5, 7, 8 are all single-agent-confirmed). Cross-agent triangulation strengthens the framing but isn't a precondition.
377
394
 
378
- Add as a new numbered section (the next number is **12** — numbering is append-only; retired instances keep their slot, see Instance 10) with the same fields: Surface / Failure shape / Recurrence / Defense status. Increment the intro paragraph's active-instance count + the Defense-pattern emergence header's `N-of-M active instances` count too.
395
+ Add as a new numbered section (the next number is **13** — numbering is append-only; retired instances keep their slot, see Instance 10) with the same fields: Surface / Failure shape / Recurrence / Defense status. Increment the intro paragraph's active-instance count + the Defense-pattern emergence header's `N-of-M active instances` count too.
379
396
 
380
397
  ---
381
398