@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
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Pure candidate-proposal pipeline for the auditor Plan membrane
3
+ * (groundnuty/macf#503, DR-026 G1). This is the Analyze→Plan half of the
4
+ * MAPE-K loop: turn F4's reflection signals into RATIFIABLE proposals.
5
+ *
6
+ * Everything here is deterministic CODE scaffolding. The LLM judgment (is this a
7
+ * real rule? is the text precise? is the tier right?) is NOT encoded — the code
8
+ * assembles the AGENT-AUTHORED signal content (`signal`/`proposed_tier`/
9
+ * `rationale` from `rule_evolution_signals`) into structured proposals and
10
+ * applies three LOAD-BEARING, mechanical safety gates:
11
+ *
12
+ * GATE 1 — N>1 = distinct AGENTS, not occurrences. A candidate is promotable
13
+ * only if it recurs across ≥ threshold DISTINCT `agent.name`s. Five hits from
14
+ * ONE agent is N=1 → HELD (reflection ≠ verification). We count the set of
15
+ * distinct agent names, never raw occurrences.
16
+ *
17
+ * GATE 3 — no-auto-drop on invariant-touch. Step 4 SURFACES which protected
18
+ * invariants a candidate touches + flags apparent-relaxations HIGH-RISK, but
19
+ * NEVER drops/rejects in code. The amendment clause lets the auditor PROPOSE
20
+ * an operator-ratified amendment, so auto-dropping would foreclose that path.
21
+ * (GATE 2 — dry-run-by-default — lives in the orchestrator/command + writer
22
+ * seam, not here.)
23
+ *
24
+ * The tier-router (step 3) groups by the `proposed_tier` HINT and marks
25
+ * `universal` candidates NEEDS-CONFIRMATION (never auto-route a universal
26
+ * promotion — it affects every deployment). The hint is a hint, not a decision.
27
+ */
28
+ import type { ReflectionRecord } from '@groundnuty/macf-core';
29
+ import { type InvariantTouch, type ProtectedInvariant } from './invariants.js';
30
+ /** Default distinct-agent threshold for promotion (configurable). */
31
+ export declare const DEFAULT_MIN_AGENTS = 2;
32
+ /**
33
+ * The router routing decision for a candidate, derived from its `proposed_tier`
34
+ * HINT. `needs-confirmation` is the universal/canonical case (never auto-route);
35
+ * `project-draft` is the local-project-rule case; `review` is anything else (an
36
+ * unrecognised tier hint still surfaces, routed for plain operator review).
37
+ */
38
+ export type RouteDecision = 'needs-confirmation' | 'project-draft' | 'review';
39
+ /** A fully-assembled, ratifiable candidate proposal (a survivor of GATE 1). */
40
+ export interface ProposalCandidate {
41
+ /** Dedup handle: the signal `key` when present, else the signal text. */
42
+ readonly handle: string;
43
+ /** Whether the grouping used an explicit `key` (vs falling back to text). */
44
+ readonly hasKey: boolean;
45
+ /** The agent-authored proposed tier HINT (verbatim). */
46
+ readonly proposedTier: string;
47
+ /** Representative agent-authored signal text. */
48
+ readonly signal: string;
49
+ /** All distinct agent-authored rationales contributing this candidate. */
50
+ readonly rationales: readonly string[];
51
+ /** Distinct agent names that corroborated this candidate (GATE 1 unit). */
52
+ readonly corroboratingAgents: readonly string[];
53
+ /** Distinct-agent count == corroboratingAgents.length (the GATE-1 number). */
54
+ readonly distinctAgents: number;
55
+ /** Raw occurrence count (records carrying this signal) — informational only. */
56
+ readonly occurrences: number;
57
+ /** Router decision from the tier hint. */
58
+ readonly route: RouteDecision;
59
+ /** Protected invariants this candidate plausibly touches (SURFACED, GATE 3). */
60
+ readonly invariantTouches: readonly InvariantTouch[];
61
+ /** True when the text reads like a relaxation → HIGH-RISK flag (GATE 3). */
62
+ readonly highRisk: boolean;
63
+ }
64
+ /** A candidate HELD below the distinct-agent threshold (reported, not dropped). */
65
+ export interface HeldCandidate {
66
+ readonly handle: string;
67
+ readonly hasKey: boolean;
68
+ readonly proposedTier: string;
69
+ readonly signal: string;
70
+ readonly distinctAgents: number;
71
+ readonly occurrences: number;
72
+ /** Why it was held — always the N<threshold reason for v1. */
73
+ readonly reason: string;
74
+ }
75
+ /** The full pipeline output: survivors + held + the threshold used. */
76
+ export interface CandidateSet {
77
+ readonly minAgents: number;
78
+ readonly promoted: readonly ProposalCandidate[];
79
+ readonly held: readonly HeldCandidate[];
80
+ }
81
+ /** Route a candidate from its tier hint (universal/canonical never auto-route). */
82
+ export declare function routeForTier(proposedTier: string): RouteDecision;
83
+ /**
84
+ * Run the full candidate pipeline over the reflection records.
85
+ *
86
+ * Steps (DR-026 G1 §The command):
87
+ * 1. Aggregate signals (distinct-agent grouping).
88
+ * 2. GATE 1 — keep candidates with distinctAgents ≥ minAgents; the rest go to
89
+ * `held` (visible, never silently dropped).
90
+ * 3. Tier-router — universal/canonical → needs-confirmation; project → draft.
91
+ * 4. Subordination-check — surface touched invariants + HIGH-RISK relaxation
92
+ * flag. NEVER drops (GATE 3).
93
+ */
94
+ export declare function buildCandidates(records: readonly ReflectionRecord[], invariants: readonly ProtectedInvariant[], minAgents?: number): CandidateSet;
95
+ //# sourceMappingURL=candidates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../../../src/cli/propose/candidates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAuB,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AAEzB,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAKpC;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,eAAe,GAAG,QAAQ,CAAC;AAE9E,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,gBAAgB,EAAE,SAAS,cAAc,EAAE,CAAC;IACrD,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAChD,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAC;CACzC;AA6DD,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,CAKhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,UAAU,EAAE,SAAS,kBAAkB,EAAE,EACzC,SAAS,GAAE,MAA2B,GACrC,YAAY,CA2Dd"}
@@ -0,0 +1,117 @@
1
+ import { invariantsTouched, readsAsRelaxation, } from './invariants.js';
2
+ /** Default distinct-agent threshold for promotion (configurable). */
3
+ export const DEFAULT_MIN_AGENTS = 2;
4
+ /** Tier values that must never auto-route (affect every deployment). */
5
+ const UNIVERSAL_TIERS = new Set(['universal', 'canonical']);
6
+ /** Group key: same handle proposed at two tiers → two distinct candidates. */
7
+ function groupKey(tier, handle) {
8
+ return JSON.stringify([tier, handle]);
9
+ }
10
+ /**
11
+ * Group all reflection rule-evolution signals by (proposed_tier, handle),
12
+ * accumulating the SET OF DISTINCT AGENT NAMES (not occurrences) for GATE 1.
13
+ *
14
+ * This is the load-bearing distinction from F4's `aggregateSignals`, which
15
+ * counts occurrences. Five signals from one agent collapse to a single-agent
16
+ * set here; five from five agents collapse to a five-agent set.
17
+ */
18
+ function groupSignals(records) {
19
+ const byKey = new Map();
20
+ for (const rec of records) {
21
+ const agentName = rec.agent.name;
22
+ for (const sig of rec.rule_evolution_signals) {
23
+ const hasKey = typeof sig.key === 'string' && sig.key.length > 0;
24
+ const handle = hasKey ? sig.key : sig.signal;
25
+ const k = groupKey(sig.proposed_tier, handle);
26
+ let g = byKey.get(k);
27
+ if (!g) {
28
+ g = {
29
+ handle,
30
+ hasKey,
31
+ proposedTier: sig.proposed_tier,
32
+ signal: sig.signal,
33
+ agents: new Set(),
34
+ rationales: [],
35
+ occurrences: 0,
36
+ };
37
+ byKey.set(k, g);
38
+ }
39
+ g.agents.add(agentName);
40
+ g.occurrences += 1;
41
+ const rationale = sig.rationale.trim();
42
+ if (rationale.length > 0 && !g.rationales.includes(rationale)) {
43
+ g.rationales.push(rationale);
44
+ }
45
+ }
46
+ }
47
+ return [...byKey.values()];
48
+ }
49
+ /** Route a candidate from its tier hint (universal/canonical never auto-route). */
50
+ export function routeForTier(proposedTier) {
51
+ const tier = proposedTier.trim().toLowerCase();
52
+ if (UNIVERSAL_TIERS.has(tier))
53
+ return 'needs-confirmation';
54
+ if (tier === 'project')
55
+ return 'project-draft';
56
+ return 'review';
57
+ }
58
+ /**
59
+ * Run the full candidate pipeline over the reflection records.
60
+ *
61
+ * Steps (DR-026 G1 §The command):
62
+ * 1. Aggregate signals (distinct-agent grouping).
63
+ * 2. GATE 1 — keep candidates with distinctAgents ≥ minAgents; the rest go to
64
+ * `held` (visible, never silently dropped).
65
+ * 3. Tier-router — universal/canonical → needs-confirmation; project → draft.
66
+ * 4. Subordination-check — surface touched invariants + HIGH-RISK relaxation
67
+ * flag. NEVER drops (GATE 3).
68
+ */
69
+ export function buildCandidates(records, invariants, minAgents = DEFAULT_MIN_AGENTS) {
70
+ const threshold = Number.isFinite(minAgents) && minAgents >= 1 ? Math.floor(minAgents) : DEFAULT_MIN_AGENTS;
71
+ const groups = groupSignals(records);
72
+ const promoted = [];
73
+ const held = [];
74
+ for (const g of groups) {
75
+ const distinctAgents = g.agents.size;
76
+ if (distinctAgents < threshold) {
77
+ held.push({
78
+ handle: g.handle,
79
+ hasKey: g.hasKey,
80
+ proposedTier: g.proposedTier,
81
+ signal: g.signal,
82
+ distinctAgents,
83
+ occurrences: g.occurrences,
84
+ reason: `N=${distinctAgents} distinct agent(s) < threshold ${threshold} (reflection ≠ verification)`,
85
+ });
86
+ continue;
87
+ }
88
+ // Step 4 — subordination-check surfacing (GATE 3: surface, never drop).
89
+ const candidateText = [g.signal, ...g.rationales].join('\n');
90
+ const touches = invariantsTouched(candidateText, invariants);
91
+ const highRisk = readsAsRelaxation(candidateText) && touches.length > 0;
92
+ promoted.push({
93
+ handle: g.handle,
94
+ hasKey: g.hasKey,
95
+ proposedTier: g.proposedTier,
96
+ signal: g.signal,
97
+ rationales: g.rationales,
98
+ corroboratingAgents: [...g.agents].sort(),
99
+ distinctAgents,
100
+ occurrences: g.occurrences,
101
+ route: routeForTier(g.proposedTier),
102
+ invariantTouches: touches,
103
+ highRisk,
104
+ });
105
+ }
106
+ // Strongest corroboration first, then high-risk surfaced near the top so the
107
+ // operator sees the constitutional-scrutiny candidates early.
108
+ promoted.sort((a, b) => Number(b.highRisk) - Number(a.highRisk) ||
109
+ b.distinctAgents - a.distinctAgents ||
110
+ a.proposedTier.localeCompare(b.proposedTier) ||
111
+ a.handle.localeCompare(b.handle));
112
+ held.sort((a, b) => b.distinctAgents - a.distinctAgents ||
113
+ a.proposedTier.localeCompare(b.proposedTier) ||
114
+ a.handle.localeCompare(b.handle));
115
+ return { minAgents: threshold, promoted, held };
116
+ }
117
+ //# sourceMappingURL=candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../../../src/cli/propose/candidates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /** One protected invariant, parsed from the ratified design doc. */
2
+ export interface ProtectedInvariant {
3
+ /** 1-based ordinal as it appears in the doc. */
4
+ readonly index: number;
5
+ /** Short bold title (the text before the first period of the list item). */
6
+ readonly title: string;
7
+ /** The full list-item line (title + gloss + refs). */
8
+ readonly text: string;
9
+ /** Lowercased keyword tokens used for the heuristic touch-match. */
10
+ readonly keywords: readonly string[];
11
+ }
12
+ /** A surfaced touchpoint: which invariant a candidate plausibly touches. */
13
+ export interface InvariantTouch {
14
+ readonly index: number;
15
+ readonly title: string;
16
+ /** Tokens that matched (for operator transparency). */
17
+ readonly matchedKeywords: readonly string[];
18
+ }
19
+ /**
20
+ * Parse the invariant list out of `protected-invariants.md`.
21
+ *
22
+ * The doc's invariants are an ordered Markdown list, each item shaped like:
23
+ * `1. **Reporter-owns-closure accountability.** The agent who ... (refs)`
24
+ * We extract the bold title, the full line, and a keyword set (title words minus
25
+ * stopwords, plus the curated set for that ordinal).
26
+ */
27
+ export declare function parseInvariants(markdown: string): readonly ProtectedInvariant[];
28
+ /**
29
+ * Load the protected-invariant set from the repo's `design/protected-invariants.md`.
30
+ *
31
+ * `repoRoot` is the framework-source repo root (where `design/` lives). Returns
32
+ * `[]` (never throws) when the file is absent — the membrane still runs and the
33
+ * subordination-check simply surfaces nothing (loud-but-proceeds). A missing
34
+ * invariant file is surfaced separately by the caller.
35
+ */
36
+ export declare function loadInvariants(repoRoot: string): readonly ProtectedInvariant[];
37
+ /**
38
+ * For a candidate's combined text, return which invariants it plausibly touches.
39
+ *
40
+ * A touch is recorded when ANY of the invariant's keywords appears (as a
41
+ * substring) in the candidate text. Generous by design (see module header).
42
+ */
43
+ export declare function invariantsTouched(candidateText: string, invariants: readonly ProtectedInvariant[]): readonly InvariantTouch[];
44
+ /**
45
+ * Whether the candidate text reads like it RELAXES a guarantee. Heuristic only —
46
+ * a `true` here means "flag HIGH-RISK for operator scrutiny", NOT "drop".
47
+ */
48
+ export declare function readsAsRelaxation(candidateText: string): boolean;
49
+ //# sourceMappingURL=invariants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariants.d.ts","sourceRoot":"","sources":["../../../src/cli/propose/invariants.ts"],"names":[],"mappings":"AAuBA,oEAAoE;AACpE,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAgCD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,kBAAkB,EAAE,CAmB/E;AAkCD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,kBAAkB,EAAE,CAU9E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,SAAS,kBAAkB,EAAE,GACxC,SAAS,cAAc,EAAE,CAU3B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAEhE"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Protected-invariant loader + subordination-check heuristics for the auditor
3
+ * Plan membrane (groundnuty/macf#503, DR-026 G1).
4
+ *
5
+ * Loads `design/protected-invariants.md` (the ratified SECP guardrail core) and
6
+ * surfaces, for a given candidate's text, WHICH invariant(s) it plausibly
7
+ * touches and whether the text *reads like* a relaxation of one.
8
+ *
9
+ * CRITICAL — this module SURFACES; it never decides. `protected-invariants.md`
10
+ * carries an amendment clause: the auditor may *propose* an operator-ratified
11
+ * amendment to an invariant, so auto-dropping a candidate that touches one would
12
+ * foreclose that constitutional path. We therefore mark — never reject. The
13
+ * weaken-vs-amend call is the operator's (v1-manual) and, later, G3's.
14
+ *
15
+ * The match is a deliberately simple keyword/heuristic pass on the invariant
16
+ * titles + a small curated keyword set per invariant. It is intentionally
17
+ * generous (favours surfacing a touch over missing one): a false-positive
18
+ * "touches invariant N" is cheap (operator eyeballs it), a false-negative is the
19
+ * dangerous direction.
20
+ */
21
+ import { readFileSync, existsSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ /**
24
+ * Per-invariant curated keyword sets. Keyed by the 1-based ordinal in
25
+ * `protected-invariants.md`. These augment the auto-extracted title words so the
26
+ * touch-match catches paraphrases the title alone would miss (e.g. "merge"
27
+ * mapping to the LGTM-gate invariant). Lowercase, matched as word-ish substrings.
28
+ */
29
+ const CURATED_KEYWORDS = {
30
+ 1: ['reporter', 'closure', 'close', 'owns', 'verify', 'verification'],
31
+ 2: ['identity', 'attribution', 'attributed', 'bot', 'token', 'impersonat'],
32
+ 3: ['merge', 'self-merge', 'lgtm', 'approve', 'approved', 'review', 'non-author'],
33
+ 4: ['routing', 'route', 'mention', '@mention', 'recipient', 'false-route'],
34
+ 5: ['auto-close', 'closes', 'fixes', 'resolves', 'refs', 'keyword'],
35
+ 6: ['fail-loud', 'silent', 'fallback', 'result-invariant', 'exit code', 'assert'],
36
+ 7: ['pr', 'pull request', 'artifact', 'rollback', 'review', 'ci'],
37
+ 8: ['auditor', 'never-acts', 'propose', 'proposes', 'merge', 'close', 'implement', 'act'],
38
+ 9: ['operator', 'ratifier', 'ratify', 'ratification', 'auto-applied', 'constitutional', 'human'],
39
+ 10: ['universal', 'locally', 'local', 'mutable', 'patch', 'upstream', 'product rule'],
40
+ };
41
+ /** Words too generic to be a meaningful touch-signal on their own. */
42
+ const STOPWORDS = new Set([
43
+ 'the', 'and', 'for', 'with', 'not', 'never', 'over', 'must', 'any',
44
+ 'every', 'that', 'this', 'its', 'are', 'all', 'who', 'how', 'a', 'an',
45
+ 'is', 'to', 'of', 'or', 'on', 'by', 'be', 'it', 'as', 'no',
46
+ ]);
47
+ /** Phrases in candidate text that read like a *relaxation* of a guarantee. */
48
+ const RELAXATION_HINT = /\b(?:relax|weaken|loosen|drop|remove|skip|bypass|disable|waive|exempt|optional(?:ly)?|allow(?:ed|s)?\s+(?:self|auto|without)|no\s+longer\s+(?:require|need)|without\s+(?:a\s+)?(?:review|approval|mention|token)|self-merge|auto-merge|auto-close)\b/i;
49
+ /**
50
+ * Parse the invariant list out of `protected-invariants.md`.
51
+ *
52
+ * The doc's invariants are an ordered Markdown list, each item shaped like:
53
+ * `1. **Reporter-owns-closure accountability.** The agent who ... (refs)`
54
+ * We extract the bold title, the full line, and a keyword set (title words minus
55
+ * stopwords, plus the curated set for that ordinal).
56
+ */
57
+ export function parseInvariants(markdown) {
58
+ const out = [];
59
+ // Only parse list items under the "## The invariants" section, so the
60
+ // "## Amending this set" numbered list (1./2./3.) is not mistaken for invariants.
61
+ const section = sliceInvariantSection(markdown);
62
+ const itemRe = /^(\d+)\.\s+\*\*(.+?)\*\*\s*(.*)$/;
63
+ for (const rawLine of section.split('\n')) {
64
+ const line = rawLine.trim();
65
+ const m = itemRe.exec(line);
66
+ if (!m)
67
+ continue;
68
+ const index = Number(m[1]);
69
+ const title = (m[2] ?? '').replace(/\.$/, '').trim();
70
+ const text = line;
71
+ const titleTokens = tokenize(title);
72
+ const curated = CURATED_KEYWORDS[index] ?? [];
73
+ const keywords = [...new Set([...titleTokens, ...curated.map((k) => k.toLowerCase())])];
74
+ out.push({ index, title, text, keywords });
75
+ }
76
+ return out;
77
+ }
78
+ /**
79
+ * Slice the markdown to the "## The invariants" section only (up to the next
80
+ * `## ` heading). Falls back to the whole doc if the heading isn't found.
81
+ */
82
+ function sliceInvariantSection(markdown) {
83
+ const lines = markdown.split('\n');
84
+ let start = -1;
85
+ for (let i = 0; i < lines.length; i += 1) {
86
+ if (/^##\s+The invariants\s*$/i.test(lines[i].trim())) {
87
+ start = i + 1;
88
+ break;
89
+ }
90
+ }
91
+ if (start < 0)
92
+ return markdown;
93
+ let end = lines.length;
94
+ for (let i = start; i < lines.length; i += 1) {
95
+ if (/^##\s+/.test(lines[i].trim())) {
96
+ end = i;
97
+ break;
98
+ }
99
+ }
100
+ return lines.slice(start, end).join('\n');
101
+ }
102
+ function tokenize(s) {
103
+ return s
104
+ .toLowerCase()
105
+ .split(/[^a-z0-9-]+/)
106
+ .map((t) => t.trim())
107
+ .filter((t) => t.length >= 3 && !STOPWORDS.has(t));
108
+ }
109
+ /**
110
+ * Load the protected-invariant set from the repo's `design/protected-invariants.md`.
111
+ *
112
+ * `repoRoot` is the framework-source repo root (where `design/` lives). Returns
113
+ * `[]` (never throws) when the file is absent — the membrane still runs and the
114
+ * subordination-check simply surfaces nothing (loud-but-proceeds). A missing
115
+ * invariant file is surfaced separately by the caller.
116
+ */
117
+ export function loadInvariants(repoRoot) {
118
+ const path = join(repoRoot, 'design', 'protected-invariants.md');
119
+ if (!existsSync(path))
120
+ return [];
121
+ let raw;
122
+ try {
123
+ raw = readFileSync(path, 'utf-8');
124
+ }
125
+ catch {
126
+ return [];
127
+ }
128
+ return parseInvariants(raw);
129
+ }
130
+ /**
131
+ * For a candidate's combined text, return which invariants it plausibly touches.
132
+ *
133
+ * A touch is recorded when ANY of the invariant's keywords appears (as a
134
+ * substring) in the candidate text. Generous by design (see module header).
135
+ */
136
+ export function invariantsTouched(candidateText, invariants) {
137
+ const hay = candidateText.toLowerCase();
138
+ const touches = [];
139
+ for (const inv of invariants) {
140
+ const matched = inv.keywords.filter((k) => hay.includes(k));
141
+ if (matched.length > 0) {
142
+ touches.push({ index: inv.index, title: inv.title, matchedKeywords: matched });
143
+ }
144
+ }
145
+ return touches;
146
+ }
147
+ /**
148
+ * Whether the candidate text reads like it RELAXES a guarantee. Heuristic only —
149
+ * a `true` here means "flag HIGH-RISK for operator scrutiny", NOT "drop".
150
+ */
151
+ export function readsAsRelaxation(candidateText) {
152
+ return RELAXATION_HINT.test(candidateText);
153
+ }
154
+ //# sourceMappingURL=invariants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariants.js","sourceRoot":"","sources":["../../../src/cli/propose/invariants.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /** The label every auditor-opened proposal issue carries (for filtering). */
2
+ export declare const PROPOSAL_LABEL = "auditor-proposal";
3
+ /** Input to open a single ratifiable proposal issue. */
4
+ export interface ProposalIssueInput {
5
+ readonly repo: string;
6
+ readonly title: string;
7
+ /** The full Markdown proposal body (assembled from agent-authored content). */
8
+ readonly body: string;
9
+ /** Labels to apply on creation; always includes `auditor-proposal`. */
10
+ readonly labels: readonly string[];
11
+ }
12
+ /** Result of opening a proposal issue. */
13
+ export interface ProposalIssueResult {
14
+ /** The created issue URL (or number) as returned by the creator. */
15
+ readonly url: string;
16
+ }
17
+ /**
18
+ * The create-only GitHub seam. Implementations MUST perform only issue
19
+ * CREATION. The membrane depends on this interface (not on `gh` directly) so
20
+ * tests can inject a fake + assert (a) zero creates in dry-run mode and (b)
21
+ * create-only behaviour under `--file`.
22
+ */
23
+ export interface ProposalIssueWriter {
24
+ createProposalIssue(input: ProposalIssueInput): Promise<ProposalIssueResult>;
25
+ }
26
+ /**
27
+ * Production writer: shells out to `gh issue create` for a SINGLE issue per
28
+ * call. `token` is forwarded as `GH_TOKEN` in the subprocess env (canonical bot
29
+ * auth). This class has no merge/close/edit/comment path at all — issue creation
30
+ * is the only operation it can perform.
31
+ */
32
+ export declare function createGhProposalWriter(token: string): ProposalIssueWriter;
33
+ //# sourceMappingURL=proposal-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposal-writer.d.ts","sourceRoot":"","sources":["../../../src/cli/propose/proposal-writer.ts"],"names":[],"mappings":"AA0BA,6EAA6E;AAC7E,eAAO,MAAM,cAAc,qBAAqB,CAAC;AAEjD,wDAAwD;AACxD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,0CAA0C;AAC1C,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC9E;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAwBzE"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Create-only GitHub seam for the auditor Plan membrane
3
+ * (groundnuty/macf#503, DR-026 G1).
4
+ *
5
+ * THE LOAD-BEARING INVARIANT (mirror of F4's read-only `GitHubReader`, inverted
6
+ * for creation): the Plan membrane may, under an explicit `--file` flag, OPEN
7
+ * ratifiable proposal artifacts — but it may do NOTHING else. This interface
8
+ * exposes ONLY `createProposalIssue`. There is deliberately NO merge / close /
9
+ * edit / comment / label-mutate / delete method on the seam, so any
10
+ * non-proposal mutation is *unrepresentable* through the type. This is invariant
11
+ * #8 (auditor-never-acts) enforced structurally at the type boundary: the
12
+ * auditor proposes, the operator ratifies — the auditor can never apply.
13
+ *
14
+ * The default implementation shells out to `gh issue create` via `execFile` (a
15
+ * single create per candidate, with the `auditor-proposal` label). The token is
16
+ * passed via `GH_TOKEN` in the subprocess env (the house bot-auth posture),
17
+ * never baked into a remote or used for any other op.
18
+ *
19
+ * Tests inject a fake implementation and assert it received CREATES only (and
20
+ * zero creates in the default dry-run mode).
21
+ */
22
+ import { execFile } from 'node:child_process';
23
+ import { promisify } from 'node:util';
24
+ const execFileAsync = promisify(execFile);
25
+ /** The label every auditor-opened proposal issue carries (for filtering). */
26
+ export const PROPOSAL_LABEL = 'auditor-proposal';
27
+ /**
28
+ * Production writer: shells out to `gh issue create` for a SINGLE issue per
29
+ * call. `token` is forwarded as `GH_TOKEN` in the subprocess env (canonical bot
30
+ * auth). This class has no merge/close/edit/comment path at all — issue creation
31
+ * is the only operation it can perform.
32
+ */
33
+ export function createGhProposalWriter(token) {
34
+ const env = { ...process.env, GH_TOKEN: token };
35
+ return {
36
+ async createProposalIssue(input) {
37
+ const labelArgs = [];
38
+ for (const label of input.labels) {
39
+ labelArgs.push('--label', label);
40
+ }
41
+ const { stdout } = await execFileAsync('gh', [
42
+ 'issue', 'create',
43
+ '--repo', input.repo,
44
+ '--title', input.title,
45
+ '--body', input.body,
46
+ ...labelArgs,
47
+ ], { encoding: 'utf-8', env, maxBuffer: 8 * 1024 * 1024 });
48
+ // `gh issue create` prints the new issue URL on stdout.
49
+ return { url: stdout.trim() };
50
+ },
51
+ };
52
+ }
53
+ //# sourceMappingURL=proposal-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposal-writer.js","sourceRoot":"","sources":["../../../src/cli/propose/proposal-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,6EAA6E;AAC7E,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,CAAC;AA4BjD;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAEhD,OAAO;QACL,KAAK,CAAC,mBAAmB,CAAC,KAAyB;YACjD,MAAM,SAAS,GAAa,EAAE,CAAC;YAC/B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjC,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACnC,CAAC;YACD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CACpC,IAAI,EACJ;gBACE,OAAO,EAAE,QAAQ;gBACjB,QAAQ,EAAE,KAAK,CAAC,IAAI;gBACpB,SAAS,EAAE,KAAK,CAAC,KAAK;gBACtB,QAAQ,EAAE,KAAK,CAAC,IAAI;gBACpB,GAAG,SAAS;aACb,EACD,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,CACvD,CAAC;YACF,wDAAwD;YACxD,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Pure formatters for the auditor Plan membrane (groundnuty/macf#503, DR-026 G1).
3
+ *
4
+ * Two outputs, both deterministic (no I/O, no clock):
5
+ * - `buildProposalBody` — the Markdown body of ONE ratifiable proposal issue,
6
+ * assembled from the AGENT-AUTHORED signal content (signal text + rationales)
7
+ * plus the mechanical metadata (tier, route, distinct-agent corroboration,
8
+ * invariant touchpoints, HIGH-RISK flag).
9
+ * - `buildReport` — the dry-run Markdown report printed to stdout/--output: the
10
+ * promoted candidates (each rendered as a proposal preview) PLUS a separate,
11
+ * visible "HELD (N<threshold)" section. The default mode opens NOTHING; this
12
+ * report IS the default-mode artifact.
13
+ *
14
+ * Every proposal makes its non-actuation explicit: the operator ratifies; the
15
+ * auditor never merges/applies (invariants #8 + #9).
16
+ */
17
+ import { type ProposalIssueInput } from './proposal-writer.js';
18
+ import type { CandidateSet, ProposalCandidate } from './candidates.js';
19
+ /** A short, stable proposal title from the candidate. */
20
+ export declare function proposalTitle(c: ProposalCandidate): string;
21
+ /**
22
+ * Build the Markdown body for one ratifiable proposal issue. Pure assembly of
23
+ * agent-authored content + mechanical metadata; no LLM judgment is encoded.
24
+ */
25
+ export declare function buildProposalBody(c: ProposalCandidate): string;
26
+ /** Build the full `ProposalIssueInput` (title + body + labels) for a candidate. */
27
+ export declare function buildProposalIssueInput(repo: string, c: ProposalCandidate): ProposalIssueInput;
28
+ export interface ReportInput {
29
+ readonly project: string;
30
+ readonly repo: string;
31
+ readonly candidates: CandidateSet;
32
+ /** True when `--file` was set (artifacts opened); false = dry-run report. */
33
+ readonly fileMode: boolean;
34
+ /** Whether `protected-invariants.md` was found + parsed (surfaced if not). */
35
+ readonly invariantsLoaded: boolean;
36
+ /** Count of reflection records read (for the summary line). */
37
+ readonly reflectionRecords: number;
38
+ /** Count of malformed reflection lines skipped (surfaced). */
39
+ readonly reflectionsSkipped: number;
40
+ /** Number of reflection ledger files read. */
41
+ readonly reflectionFiles: number;
42
+ }
43
+ /**
44
+ * Build the dry-run Markdown report. Promoted candidates render as previews; the
45
+ * HELD set renders in its own clearly-labelled section so a sub-threshold
46
+ * candidate is VISIBLE, never silently dropped.
47
+ */
48
+ export declare function buildReport(input: ReportInput): string;
49
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../src/cli/propose/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AActF,yDAAyD;AACzD,wBAAgB,aAAa,CAAC,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAK1D;AAmCD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,iBAAiB,GAAG,MAAM,CA0D9D;AAED,mFAAmF;AACnF,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,iBAAiB,GACnB,kBAAkB,CAUpB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,8DAA8D;IAC9D,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,8CAA8C;IAC9C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AA4BD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAqFtD"}