@mmnto/totem 1.102.0 → 1.103.0

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 (39) hide show
  1. package/dist/artifacts/verdict.d.ts +70 -14
  2. package/dist/artifacts/verdict.d.ts.map +1 -1
  3. package/dist/artifacts/verdict.js +43 -3
  4. package/dist/artifacts/verdict.js.map +1 -1
  5. package/dist/artifacts/verdict.test.js +169 -1
  6. package/dist/artifacts/verdict.test.js.map +1 -1
  7. package/dist/autoclose/index.d.ts +12 -0
  8. package/dist/autoclose/index.d.ts.map +1 -0
  9. package/dist/autoclose/index.js +9 -0
  10. package/dist/autoclose/index.js.map +1 -0
  11. package/dist/autoclose/matcher.d.ts +115 -0
  12. package/dist/autoclose/matcher.d.ts.map +1 -0
  13. package/dist/autoclose/matcher.js +164 -0
  14. package/dist/autoclose/matcher.js.map +1 -0
  15. package/dist/autoclose/matcher.test.d.ts +2 -0
  16. package/dist/autoclose/matcher.test.d.ts.map +1 -0
  17. package/dist/autoclose/matcher.test.js +133 -0
  18. package/dist/autoclose/matcher.test.js.map +1 -0
  19. package/dist/autoclose/merge-config.d.ts +73 -0
  20. package/dist/autoclose/merge-config.d.ts.map +1 -0
  21. package/dist/autoclose/merge-config.js +81 -0
  22. package/dist/autoclose/merge-config.js.map +1 -0
  23. package/dist/autoclose/merge-config.test.d.ts +2 -0
  24. package/dist/autoclose/merge-config.test.d.ts.map +1 -0
  25. package/dist/autoclose/merge-config.test.js +108 -0
  26. package/dist/autoclose/merge-config.test.js.map +1 -0
  27. package/dist/autoclose/receipt.d.ts +160 -0
  28. package/dist/autoclose/receipt.d.ts.map +1 -0
  29. package/dist/autoclose/receipt.js +296 -0
  30. package/dist/autoclose/receipt.js.map +1 -0
  31. package/dist/autoclose/receipt.test.d.ts +2 -0
  32. package/dist/autoclose/receipt.test.d.ts.map +1 -0
  33. package/dist/autoclose/receipt.test.js +248 -0
  34. package/dist/autoclose/receipt.test.js.map +1 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/package.json +1 -1
@@ -0,0 +1,133 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { AUTO_CLOSE_KEYWORDS, AUTO_CLOSE_REGEX_SOURCE, autoCloseKeyForms, findAutoCloseRefs, } from './matcher.js';
3
+ /** Verbatim body of commit b8aa74a2 on main — the real #2471→#2466 specimen. */
4
+ const B8AA74A2_BODY = 'fix(review): deterministic skip paths no longer stamp the push gate (#2466) (#2471)\n' +
5
+ '\n' +
6
+ 'Three deterministic skip paths (all-generated, all-non-code, filtered-empty) no longer ' +
7
+ 'mint the reviewed-content stamp; they log a shared NON-REVIEW notice instead. Adds coverage ' +
8
+ 'for the .gitattributes-marked .ts bypass and the filtered-empty path. ' +
9
+ 'Does not close #2466 (live exit-0 half deferred to #2473).';
10
+ const refs = (t) => findAutoCloseRefs(t).map((m) => m.ref);
11
+ describe('AUTO_CLOSE_REGEX_SOURCE / findAutoCloseRefs', () => {
12
+ it('exports the nine GitHub close keywords', () => {
13
+ expect([...AUTO_CLOSE_KEYWORDS].sort()).toEqual([
14
+ 'close',
15
+ 'closed',
16
+ 'closes',
17
+ 'fix',
18
+ 'fixed',
19
+ 'fixes',
20
+ 'resolve',
21
+ 'resolved',
22
+ 'resolves',
23
+ ].sort());
24
+ });
25
+ it('compiles to a valid regex', () => {
26
+ expect(() => new RegExp(AUTO_CLOSE_REGEX_SOURCE, 'gi')).not.toThrow();
27
+ });
28
+ // ─── presence invariant: negated / emphasized / quoted all match ──────────
29
+ it('matches under NEGATION (the confirmed #2471 specimen)', () => {
30
+ expect(refs('Does not close #2466')).toEqual(['#2466']);
31
+ });
32
+ it('matches inside EMPHASIS', () => {
33
+ expect(refs('**Fixes #700**')).toEqual(['#700']);
34
+ });
35
+ it('matches inside a block QUOTE', () => {
36
+ expect(refs('> quoted: closes #12')).toEqual(['#12']);
37
+ });
38
+ it('matches a QUALIFIED cross-repo ref (still auto-closes)', () => {
39
+ expect(refs('This resolves mmnto-ai/totem#2466 upstream')).toEqual(['mmnto-ai/totem#2466']);
40
+ });
41
+ // ─── issue/PR URL form (kimi BLOCKING-1) ──────────────────────────────────
42
+ // GitHub's docs list only `#N` and `owner/repo#N` as closing syntaxes
43
+ // (docs.github.com/.../linking-a-pull-request-to-an-issue, verified: no URL
44
+ // form documented). The URL form is UNDOCUMENTED but EMPIRICALLY closes —
45
+ // isaacs/github#1731 (comment-permalink over-fire) + SAP/spartacus CONTRIBUTING
46
+ // (`Fixes https://github.com/SAP/spartacus/issues/<n>`). Matched
47
+ // presence-invariantly, normalized to the same `owner/repo#N` key.
48
+ it('matches the issue-URL form, normalized to owner/repo#N', () => {
49
+ expect(refs('Fixes https://github.com/mmnto-ai/totem/issues/2466')).toEqual([
50
+ 'mmnto-ai/totem#2466',
51
+ ]);
52
+ });
53
+ it('tolerates a trailing #issuecomment- permalink fragment on the URL', () => {
54
+ expect(refs('Fix https://github.com/o/r/issues/123#issuecomment-999')).toEqual(['o/r#123']);
55
+ });
56
+ it('matches the /pull/ URL form too', () => {
57
+ expect(refs('closes https://github.com/o/r/pull/45')).toEqual(['o/r#45']);
58
+ });
59
+ it('matches MULTIPLE refs including a colon separator', () => {
60
+ expect(refs('Fixes: #55 and also Resolved #56')).toEqual(['#55', '#56']);
61
+ });
62
+ it('matches every keyword inflection', () => {
63
+ for (const kw of AUTO_CLOSE_KEYWORDS) {
64
+ expect(refs(`${kw} #1`)).toEqual(['#1']);
65
+ expect(refs(`${kw.toUpperCase()} #2`)).toEqual(['#2']);
66
+ }
67
+ });
68
+ it('preserves the keyword casing and records a match index', () => {
69
+ const [m] = findAutoCloseRefs('xx Closes #9');
70
+ expect(m?.keyword).toBe('Closes');
71
+ expect(m?.issue).toBe(9);
72
+ expect(m?.index).toBe(3);
73
+ });
74
+ // ─── non-matches ──────────────────────────────────────────────────────────
75
+ it('does NOT match subject-line parentheticals (no adjacent keyword)', () => {
76
+ expect(refs('fix(review): stamp the push gate (#2466) (#2471)')).toEqual([]);
77
+ });
78
+ it('does NOT match keyword substrings (prefix / affixes / fixup)', () => {
79
+ expect(refs('prefix #99, affixes #98, fixup #97')).toEqual([]);
80
+ });
81
+ it('does NOT match with no separator (closed#88 — GitHub declines it too)', () => {
82
+ expect(refs('closed#88')).toEqual([]);
83
+ });
84
+ it('does NOT match a bare ref with no keyword', () => {
85
+ expect(refs('See #247 above')).toEqual([]);
86
+ });
87
+ it('returns [] for empty / non-string input', () => {
88
+ expect(findAutoCloseRefs('')).toEqual([]);
89
+ expect(findAutoCloseRefs(undefined)).toEqual([]);
90
+ });
91
+ // ─── DELIBERATE pinned behavior (kimi Q1/Q2/NB-2/NB-3) ────────────────────
92
+ // These pin CURRENT behavior; the empirical GitHub questions are routed to the
93
+ // arming-phase sandbox matrix (spec §arming "open empirical questions").
94
+ it('DELIBERATE: emphasis/backtick wrapping ONLY the keyword => MISS', () => {
95
+ // `**`/backtick severs keyword→ref adjacency in the raw text.
96
+ expect(refs('**closes** #123')).toEqual([]);
97
+ expect(refs('`closes` #123')).toEqual([]);
98
+ });
99
+ it('DELIBERATE: the GH-123 autolink form => MISS (undocumented for closing)', () => {
100
+ expect(refs('fix GH-123')).toEqual([]);
101
+ });
102
+ it('DELIBERATE: a cross-paragraph span => MATCH (over-fires, invariant-consistent)', () => {
103
+ expect(refs('closes\n\n#123')).toEqual(['#123']);
104
+ });
105
+ it('DELIBERATE: a fenced code block => MATCH (over-fires; totem-context is the escape)', () => {
106
+ expect(refs('```\ncloses #123\n```')).toEqual(['#123']);
107
+ });
108
+ // ─── perf sanity (kimi NON-BLOCKING-1: the O(n²) is dead) ──────────────────
109
+ it('completes a pathological long-whitespace probe in well under 50ms', () => {
110
+ const probe = `fix${' '.repeat(65000)}x`; // keyword + ~64KB whitespace + non-ref
111
+ const t0 = performance.now();
112
+ findAutoCloseRefs(probe);
113
+ expect(performance.now() - t0).toBeLessThan(50);
114
+ });
115
+ // ─── the b8aa74a2 positive control ────────────────────────────────────────
116
+ it('finds exactly #2466 in the verbatim b8aa74a2 body (and NOT the parentheticals)', () => {
117
+ expect(refs(B8AA74A2_BODY)).toEqual(['#2466']);
118
+ });
119
+ });
120
+ describe('autoCloseKeyForms', () => {
121
+ it('expands a bare ref to include the self-repo qualified form', () => {
122
+ expect(autoCloseKeyForms({ issue: 2466 }, 'mmnto-ai/totem').sort()).toEqual(['#2466', 'mmnto-ai/totem#2466'].sort());
123
+ });
124
+ it('expands a self-qualified ref to include the bare form', () => {
125
+ expect(autoCloseKeyForms({ qualifier: 'mmnto-ai/totem', issue: 2466 }, 'mmnto-ai/totem').sort()).toEqual(['#2466', 'mmnto-ai/totem#2466'].sort());
126
+ });
127
+ it('keeps a cross-repo qualified ref distinct from the bare form', () => {
128
+ expect(autoCloseKeyForms({ qualifier: 'mmnto-ai/other', issue: 5 }, 'mmnto-ai/totem')).toEqual([
129
+ 'mmnto-ai/other#5',
130
+ ]);
131
+ });
132
+ });
133
+ //# sourceMappingURL=matcher.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.test.js","sourceRoot":"","sources":["../../src/autoclose/matcher.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Repo merge-config posture assertion for the auto-close enforcement seam
3
+ * (mmnto-ai/totem#1762, E-lever addendum — strategy-claude 2026-07-21T0235Z; the
4
+ * squash-only extension — codex supplement 2026-07-21T0356Z).
5
+ *
6
+ * The operator flipped all four governed repos to
7
+ * `squash_merge_commit_title: PR_TITLE` + `squash_merge_commit_message: BLANK`
8
+ * (executed 2026-07-21T02:30Z). BLANK stops GitHub composing a squash body from
9
+ * COMMIT_MESSAGES, so the server-composed close-keyword channel is gone; PR_TITLE
10
+ * makes the squash subject deterministically the PR title (a surface D1 already
11
+ * scans pre-merge).
12
+ *
13
+ * D1 also asserts SQUASH-ONLY (`allow_squash_merge` on, `allow_merge_commit` and
14
+ * `allow_rebase_merge` off). This is what LICENSES D2's single-HEAD, `(#N)`-subject
15
+ * assumption: a rebase merge pushes several commits (an earlier close-keyword
16
+ * commit would never be scanned when only the final SHA is read), and a regular
17
+ * merge commit need not carry the squash-subject form (breaking receipt
18
+ * correlation). Enabling squash-only is an OPERATOR settings action (routed
19
+ * separately, like the E lever); until flipped, D1 reds loudly by design.
20
+ *
21
+ * A repo setting is one careless settings-page click from silently reverting — so
22
+ * D1 ASSERTS the whole posture at workflow start and fails loud on drift (Tenet 4
23
+ * parity-test rule applied to repo config; a "keep it set" intention is not a
24
+ * coupling mechanism). Candidate standing row for the mmnto-ai/totem-strategy#482
25
+ * parity manifest.
26
+ *
27
+ * Pure evaluator — thin GraphQL glue lives in the D1 script. The source is
28
+ * GraphQL (`squashMergeAllowed` / `mergeCommitAllowed` / `rebaseMergeAllowed` /
29
+ * `squashMergeCommitTitle` / `squashMergeCommitMessage`), NOT the REST repos
30
+ * endpoint: REST omits the merge-policy fields for callers without admin
31
+ * visibility, and the Actions `GITHUB_TOKEN` is such a caller — discovered on
32
+ * D1's first live run, where a healthy posture read as all-absent. That failure
33
+ * class gets its own verdict (`unverifiable`) so a token-visibility problem is
34
+ * never misreported as settings drift.
35
+ */
36
+ /** The required squash-merge posture (the E lever). */
37
+ export declare const REQUIRED_SQUASH_MERGE_TITLE = "PR_TITLE";
38
+ export declare const REQUIRED_SQUASH_MERGE_MESSAGE = "BLANK";
39
+ /** The repo-config fields the posture assertion reads (absent/null = not visible). */
40
+ export interface MergeConfigPosture {
41
+ squash_merge_commit_title?: string | null;
42
+ squash_merge_commit_message?: string | null;
43
+ allow_squash_merge?: boolean | null;
44
+ allow_merge_commit?: boolean | null;
45
+ allow_rebase_merge?: boolean | null;
46
+ }
47
+ /**
48
+ * Verdict class: `drift` = a field is PRESENT and wrong (settings action needed);
49
+ * `unverifiable` = no field is wrong but at least one is absent from the config
50
+ * source (token-visibility failure — do NOT touch settings on this verdict).
51
+ */
52
+ export type MergeConfigStatus = 'conforms' | 'drift' | 'unverifiable';
53
+ /** Verdict of {@link evaluateMergeConfigPosture}. */
54
+ export interface MergeConfigVerdict {
55
+ /** True iff every field is present AND matches the required posture. */
56
+ conforms: boolean;
57
+ status: MergeConfigStatus;
58
+ /** Human-readable descriptions of each present-and-wrong field. */
59
+ drift: string[];
60
+ /** Field names the config source did not carry (token-visibility class). */
61
+ absent: string[];
62
+ /** Precise operator-facing message for the job log. */
63
+ message: string;
64
+ }
65
+ /**
66
+ * Evaluate whether a repo's merge config matches the required posture: the
67
+ * E-lever squash-body shape (`PR_TITLE` + `BLANK`) AND squash-only (`allow_squash_
68
+ * merge` on, merge-commit and rebase off). Returns the drift set (one entry per
69
+ * off-posture field) and a precise message; the caller fails the check when
70
+ * `!conforms`.
71
+ */
72
+ export declare function evaluateMergeConfigPosture(config: MergeConfigPosture): MergeConfigVerdict;
73
+ //# sourceMappingURL=merge-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-config.d.ts","sourceRoot":"","sources":["../../src/autoclose/merge-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,uDAAuD;AACvD,eAAO,MAAM,2BAA2B,aAAa,CAAC;AACtD,eAAO,MAAM,6BAA6B,UAAU,CAAC;AAErD,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IACjC,yBAAyB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,kBAAkB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,kBAAkB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,kBAAkB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,cAAc,CAAC;AAEtE,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,mEAAmE;IACnE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAiDzF"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Repo merge-config posture assertion for the auto-close enforcement seam
3
+ * (mmnto-ai/totem#1762, E-lever addendum — strategy-claude 2026-07-21T0235Z; the
4
+ * squash-only extension — codex supplement 2026-07-21T0356Z).
5
+ *
6
+ * The operator flipped all four governed repos to
7
+ * `squash_merge_commit_title: PR_TITLE` + `squash_merge_commit_message: BLANK`
8
+ * (executed 2026-07-21T02:30Z). BLANK stops GitHub composing a squash body from
9
+ * COMMIT_MESSAGES, so the server-composed close-keyword channel is gone; PR_TITLE
10
+ * makes the squash subject deterministically the PR title (a surface D1 already
11
+ * scans pre-merge).
12
+ *
13
+ * D1 also asserts SQUASH-ONLY (`allow_squash_merge` on, `allow_merge_commit` and
14
+ * `allow_rebase_merge` off). This is what LICENSES D2's single-HEAD, `(#N)`-subject
15
+ * assumption: a rebase merge pushes several commits (an earlier close-keyword
16
+ * commit would never be scanned when only the final SHA is read), and a regular
17
+ * merge commit need not carry the squash-subject form (breaking receipt
18
+ * correlation). Enabling squash-only is an OPERATOR settings action (routed
19
+ * separately, like the E lever); until flipped, D1 reds loudly by design.
20
+ *
21
+ * A repo setting is one careless settings-page click from silently reverting — so
22
+ * D1 ASSERTS the whole posture at workflow start and fails loud on drift (Tenet 4
23
+ * parity-test rule applied to repo config; a "keep it set" intention is not a
24
+ * coupling mechanism). Candidate standing row for the mmnto-ai/totem-strategy#482
25
+ * parity manifest.
26
+ *
27
+ * Pure evaluator — thin GraphQL glue lives in the D1 script. The source is
28
+ * GraphQL (`squashMergeAllowed` / `mergeCommitAllowed` / `rebaseMergeAllowed` /
29
+ * `squashMergeCommitTitle` / `squashMergeCommitMessage`), NOT the REST repos
30
+ * endpoint: REST omits the merge-policy fields for callers without admin
31
+ * visibility, and the Actions `GITHUB_TOKEN` is such a caller — discovered on
32
+ * D1's first live run, where a healthy posture read as all-absent. That failure
33
+ * class gets its own verdict (`unverifiable`) so a token-visibility problem is
34
+ * never misreported as settings drift.
35
+ */
36
+ /** The required squash-merge posture (the E lever). */
37
+ export const REQUIRED_SQUASH_MERGE_TITLE = 'PR_TITLE';
38
+ export const REQUIRED_SQUASH_MERGE_MESSAGE = 'BLANK';
39
+ /**
40
+ * Evaluate whether a repo's merge config matches the required posture: the
41
+ * E-lever squash-body shape (`PR_TITLE` + `BLANK`) AND squash-only (`allow_squash_
42
+ * merge` on, merge-commit and rebase off). Returns the drift set (one entry per
43
+ * off-posture field) and a precise message; the caller fails the check when
44
+ * `!conforms`.
45
+ */
46
+ export function evaluateMergeConfigPosture(config) {
47
+ const drift = [];
48
+ const absent = [];
49
+ const check = (name, value, required) => {
50
+ if (value === null || value === undefined) {
51
+ absent.push(name);
52
+ }
53
+ else if (value !== required) {
54
+ drift.push(`${name} is ${typeof value === 'string' ? `"${value}"` : String(value)}, expected ${typeof required === 'string' ? `"${required}"` : String(required)}`);
55
+ }
56
+ };
57
+ check('squash_merge_commit_title', config.squash_merge_commit_title, REQUIRED_SQUASH_MERGE_TITLE);
58
+ check('squash_merge_commit_message', config.squash_merge_commit_message, REQUIRED_SQUASH_MERGE_MESSAGE);
59
+ // Squash-only: squash on, merge-commit + rebase off (codex supplement — this
60
+ // licenses D2's single-HEAD `(#N)`-subject assumption).
61
+ check('allow_squash_merge', config.allow_squash_merge, true);
62
+ check('allow_merge_commit', config.allow_merge_commit, false);
63
+ check('allow_rebase_merge', config.allow_rebase_merge, false);
64
+ const status = drift.length > 0 ? 'drift' : absent.length > 0 ? 'unverifiable' : 'conforms';
65
+ const message = status === 'conforms'
66
+ ? `Merge-config posture conforms: E lever (${REQUIRED_SQUASH_MERGE_TITLE} + ${REQUIRED_SQUASH_MERGE_MESSAGE}) + squash-only.`
67
+ : status === 'drift'
68
+ ? `Merge-config posture DRIFTED from the required E-lever + squash-only posture: ` +
69
+ `${drift.join('; ')}${absent.length > 0 ? ` (also not visible to this token: ${absent.join(', ')})` : ''}. ` +
70
+ 'Re-set it in repo Settings > General > Pull Requests before ' +
71
+ 'merging — a reverted posture re-opens the server-composed close-keyword channel or ' +
72
+ 'defeats D2 single-HEAD receipt correlation. mmnto-ai/totem#1762.'
73
+ : `Merge-config posture UNVERIFIABLE — the config source did not carry: ${absent.join(', ')}. ` +
74
+ 'This is a token-visibility failure, NOT settings drift — do not touch repo settings on this ' +
75
+ 'message. The REST repos endpoint omits merge-policy fields for non-admin callers (the Actions ' +
76
+ 'GITHUB_TOKEN is one); the D1 glue must read the posture via GraphQL (squashMergeAllowed / ' +
77
+ 'mergeCommitAllowed / rebaseMergeAllowed / squashMergeCommitTitle / squashMergeCommitMessage), ' +
78
+ 'which a plain read token can see. mmnto-ai/totem#1762.';
79
+ return { conforms: status === 'conforms', status, drift, absent, message };
80
+ }
81
+ //# sourceMappingURL=merge-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-config.js","sourceRoot":"","sources":["../../src/autoclose/merge-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,uDAAuD;AACvD,MAAM,CAAC,MAAM,2BAA2B,GAAG,UAAU,CAAC;AACtD,MAAM,CAAC,MAAM,6BAA6B,GAAG,OAAO,CAAC;AA+BrD;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAA0B;IACnE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,MAAM,KAAK,GAAG,CACZ,IAAY,EACZ,KAA0C,EAC1C,QAA0B,EACpB,EAAE;QACR,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CACR,GAAG,IAAI,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CACxJ,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,CAAC,2BAA2B,EAAE,MAAM,CAAC,yBAAyB,EAAE,2BAA2B,CAAC,CAAC;IAClG,KAAK,CACH,6BAA6B,EAC7B,MAAM,CAAC,2BAA2B,EAClC,6BAA6B,CAC9B,CAAC;IACF,6EAA6E;IAC7E,wDAAwD;IACxD,KAAK,CAAC,oBAAoB,EAAE,MAAM,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;IAC7D,KAAK,CAAC,oBAAoB,EAAE,MAAM,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;IAC9D,KAAK,CAAC,oBAAoB,EAAE,MAAM,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;IAE9D,MAAM,MAAM,GACV,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC;IAC/E,MAAM,OAAO,GACX,MAAM,KAAK,UAAU;QACnB,CAAC,CAAC,2CAA2C,2BAA2B,MAAM,6BAA6B,kBAAkB;QAC7H,CAAC,CAAC,MAAM,KAAK,OAAO;YAClB,CAAC,CAAC,gFAAgF;gBAChF,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,qCAAqC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI;gBAC5G,8DAA8D;gBAC9D,qFAAqF;gBACrF,kEAAkE;YACpE,CAAC,CAAC,wEAAwE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBAC7F,8FAA8F;gBAC9F,gGAAgG;gBAChG,4FAA4F;gBAC5F,gGAAgG;gBAChG,wDAAwD,CAAC;IAEjE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7E,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=merge-config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-config.test.d.ts","sourceRoot":"","sources":["../../src/autoclose/merge-config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,108 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { evaluateMergeConfigPosture } from './merge-config.js';
3
+ /** A fully-conforming posture: E lever (PR_TITLE + BLANK) + squash-only. */
4
+ const CONFORMING = {
5
+ squash_merge_commit_title: 'PR_TITLE',
6
+ squash_merge_commit_message: 'BLANK',
7
+ allow_squash_merge: true,
8
+ allow_merge_commit: false,
9
+ allow_rebase_merge: false,
10
+ };
11
+ describe('evaluateMergeConfigPosture (D1 posture assertion, #1762 E-lever + squash-only)', () => {
12
+ it('conforms when all five fields match the required posture', () => {
13
+ const v = evaluateMergeConfigPosture(CONFORMING);
14
+ expect(v.conforms).toBe(true);
15
+ expect(v.drift).toEqual([]);
16
+ expect(v.message).toMatch(/conforms/i);
17
+ });
18
+ it('flags title drift', () => {
19
+ const v = evaluateMergeConfigPosture({
20
+ ...CONFORMING,
21
+ squash_merge_commit_title: 'COMMIT_OR_PR_TITLE',
22
+ });
23
+ expect(v.conforms).toBe(false);
24
+ expect(v.drift).toHaveLength(1);
25
+ expect(v.drift[0]).toMatch(/squash_merge_commit_title/);
26
+ expect(v.message).toMatch(/DRIFTED/);
27
+ });
28
+ it('flags message drift', () => {
29
+ const v = evaluateMergeConfigPosture({
30
+ ...CONFORMING,
31
+ squash_merge_commit_message: 'COMMIT_MESSAGES',
32
+ });
33
+ expect(v.conforms).toBe(false);
34
+ expect(v.drift).toHaveLength(1);
35
+ expect(v.drift[0]).toMatch(/squash_merge_commit_message/);
36
+ });
37
+ it('flags allow_squash_merge drift (must be on)', () => {
38
+ const v = evaluateMergeConfigPosture({ ...CONFORMING, allow_squash_merge: false });
39
+ expect(v.conforms).toBe(false);
40
+ expect(v.drift[0]).toMatch(/allow_squash_merge/);
41
+ });
42
+ it('flags allow_merge_commit drift (must be off — codex supplement)', () => {
43
+ const v = evaluateMergeConfigPosture({ ...CONFORMING, allow_merge_commit: true });
44
+ expect(v.conforms).toBe(false);
45
+ expect(v.drift[0]).toMatch(/allow_merge_commit/);
46
+ });
47
+ it('flags allow_rebase_merge drift (must be off — codex supplement)', () => {
48
+ const v = evaluateMergeConfigPosture({ ...CONFORMING, allow_rebase_merge: true });
49
+ expect(v.conforms).toBe(false);
50
+ expect(v.drift[0]).toMatch(/allow_rebase_merge/);
51
+ });
52
+ it('flags EVERY axis when all five drift', () => {
53
+ const v = evaluateMergeConfigPosture({
54
+ squash_merge_commit_title: 'COMMIT_OR_PR_TITLE',
55
+ squash_merge_commit_message: 'COMMIT_MESSAGES',
56
+ allow_squash_merge: false,
57
+ allow_merge_commit: true,
58
+ allow_rebase_merge: true,
59
+ });
60
+ expect(v.conforms).toBe(false);
61
+ expect(v.drift).toHaveLength(5);
62
+ });
63
+ it('all fields absent → UNVERIFIABLE, never reported as drift (token-visibility class)', () => {
64
+ // D1's first live run: the Actions GITHUB_TOKEN cannot see REST merge-policy
65
+ // fields, so a HEALTHY posture read as all-absent and was misreported as
66
+ // drift. Absent must be its own verdict with its own remedy (fix the read
67
+ // source, not the settings).
68
+ const v = evaluateMergeConfigPosture({});
69
+ expect(v.conforms).toBe(false);
70
+ expect(v.status).toBe('unverifiable');
71
+ expect(v.drift).toEqual([]);
72
+ expect(v.absent).toHaveLength(5);
73
+ expect(v.message).toMatch(/UNVERIFIABLE/);
74
+ expect(v.message).toMatch(/token-visibility/);
75
+ expect(v.message).not.toMatch(/DRIFTED/);
76
+ });
77
+ it('null fields (GraphQL null) behave as absent, not as drift', () => {
78
+ const v = evaluateMergeConfigPosture({
79
+ ...CONFORMING,
80
+ squash_merge_commit_title: null,
81
+ });
82
+ expect(v.status).toBe('unverifiable');
83
+ expect(v.absent).toEqual(['squash_merge_commit_title']);
84
+ });
85
+ it('a present-and-wrong field wins over absent fields: status=drift, absentees named', () => {
86
+ const v = evaluateMergeConfigPosture({
87
+ allow_merge_commit: true,
88
+ });
89
+ expect(v.status).toBe('drift');
90
+ expect(v.drift).toHaveLength(1);
91
+ expect(v.absent).toHaveLength(4);
92
+ expect(v.message).toMatch(/DRIFTED/);
93
+ expect(v.message).toMatch(/not visible to this token/);
94
+ });
95
+ it('the current live totem posture (all three methods on) drifts on the two off-axes', () => {
96
+ // codex supplement: live gh api reported allow_merge_commit=true,
97
+ // allow_rebase_merge=true, allow_squash_merge=true — so squash-only fails
98
+ // until the operator flips it (D1 reds loudly by design until then).
99
+ const v = evaluateMergeConfigPosture({
100
+ ...CONFORMING,
101
+ allow_merge_commit: true,
102
+ allow_rebase_merge: true,
103
+ });
104
+ expect(v.conforms).toBe(false);
105
+ expect(v.drift).toHaveLength(2);
106
+ });
107
+ });
108
+ //# sourceMappingURL=merge-config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-config.test.js","sourceRoot":"","sources":["../../src/autoclose/merge-config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Declared-intent receipt + reconciliation for the auto-close enforcement seam
3
+ * (mmnto-ai/totem#1762).
4
+ *
5
+ * D1 (PR-time required check) scans the PR corpus — title, description, and ALL
6
+ * branch commit messages (config-verified: the governed repos compose squash
7
+ * bodies from `COMMIT_MESSAGES`, so every branch commit is a squash-seed input)
8
+ * — for close-keyword-adjacent refs via {@link findAutoCloseRefs}, and fails on
9
+ * any ref that is not AUTHORIZED.
10
+ *
11
+ * AUTHORIZATION IS PROVENANCE-DISTINCT (codex #3 — the circularity fix). GitHub
12
+ * DERIVES `closingIssuesReferences` FROM the PR body's own close keywords, so a
13
+ * body keyword would self-whitelist against it. The ONLY authorizing channel is
14
+ * therefore the provenance-distinct `totem-close` marker (an HTML comment or a
15
+ * `Totem-Close:` trailer the author writes — see {@link parseDeclaredCloseIntent}).
16
+ * `closingIssuesReferences` is recorded on the receipt as OBSERVED GitHub state
17
+ * (informational), never as an authorization. Author workflow: declare every
18
+ * intended close with the marker.
19
+ *
20
+ * D2 (post-merge reconciliation, OBSERVATION MODE) compares the merged HEAD
21
+ * commit message against the receipt via {@link reconcile}. It alerts loud —
22
+ * never auto-reopens — until positive+negative controls arm enforcement (the
23
+ * Tenet 9 sense→enforce gate).
24
+ *
25
+ * Never scan issue/PR COMMENT bodies anywhere — comments never auto-close.
26
+ */
27
+ export declare const AUTO_CLOSE_RECEIPT_SCHEMA_VERSION = 2;
28
+ /**
29
+ * A GitHub `closingIssuesReferences` node (from the PR GraphQL/REST API): the
30
+ * issue GitHub itself recognizes as a linked closing reference for the PR.
31
+ * OBSERVED state — informational only, NEVER authorizing (GitHub derives it from
32
+ * body keywords).
33
+ */
34
+ export interface ClosingIssueRef {
35
+ number: number;
36
+ /** `owner/repo` the issue lives in, when cross-repo; omitted for same-repo. */
37
+ repoWithOwner?: string;
38
+ }
39
+ /**
40
+ * The durable D1→D2 receipt. `declaredByMarker` is the AUTHORIZING set (marker
41
+ * provenance only); `closingIssuesReferences` is OBSERVED GitHub state recorded
42
+ * for the audit trail but not used to authorize. Persisted as a GitHub Actions
43
+ * artifact keyed to the PR (see the D1 workflow script).
44
+ */
45
+ export interface AutoCloseReceipt {
46
+ schemaVersion: number;
47
+ /** `owner/repo` the PR targets. */
48
+ repo: string;
49
+ prNumber: number;
50
+ /** PR head SHA at D1 time (lets D2 correlate the artifact to the merge). */
51
+ headSha: string;
52
+ /**
53
+ * AUTHORIZING set: normalized keys declared via the provenance-distinct
54
+ * `totem-close` marker/trailer ONLY. reconcile authorizes against THIS set.
55
+ */
56
+ declaredByMarker: string[];
57
+ /**
58
+ * INFORMATIONAL: GitHub's derived `closingIssuesReferences` (normalized keys).
59
+ * Recorded for the audit trail; NOT authorizing (breaks the self-whitelist
60
+ * circularity — codex #3).
61
+ */
62
+ closingIssuesReferences: string[];
63
+ /** Normalized keys the D1 corpus scan found (audit only). */
64
+ corpusFindings: string[];
65
+ generatedAt: string;
66
+ note: string;
67
+ }
68
+ /** A structured-intent reference the author explicitly declared. */
69
+ export interface DeclaredIntentRef {
70
+ qualifier?: string;
71
+ issue: number;
72
+ }
73
+ /**
74
+ * Parse the structured-intent declarations out of `text`. Returns the refs the
75
+ * author explicitly whitelisted for closure. Does NOT interpret close keywords —
76
+ * a marker carries bare/qualified refs only.
77
+ */
78
+ export declare function parseDeclaredCloseIntent(text: string): DeclaredIntentRef[];
79
+ /** The D1 corpus surfaces (never includes comment bodies). */
80
+ export interface PrCorpus {
81
+ title: string;
82
+ body: string;
83
+ commitMessages: string[];
84
+ closingIssuesReferences: ClosingIssueRef[];
85
+ repo: string;
86
+ }
87
+ /** Result of the D1 corpus scan. `ok` iff nothing unauthorized was found. */
88
+ export interface PrScanResult {
89
+ ok: boolean;
90
+ /** Normalized keys found across the corpus. */
91
+ findings: string[];
92
+ /** AUTHORIZING set (marker-declared only) — the receipt payload. */
93
+ declaredByMarker: string[];
94
+ /** INFORMATIONAL: GitHub's derived closingIssuesReferences (normalized keys). */
95
+ closingIssuesReferences: string[];
96
+ /** Findings not authorized by the marker set — these fail the check. */
97
+ undeclared: string[];
98
+ }
99
+ /**
100
+ * D1: scan the PR corpus (title + body + every branch commit message) for
101
+ * close-keyword-adjacent refs and split them into authorized vs undeclared. A
102
+ * finding is authorized ONLY by the provenance-distinct `totem-close` marker —
103
+ * NOT by GitHub's `closingIssuesReferences` (which GitHub derives from the same
104
+ * body keywords, so it would self-whitelist; codex #3). Comment bodies are NEVER
105
+ * part of the corpus.
106
+ */
107
+ export declare function scanPrCorpus(corpus: PrCorpus): PrScanResult;
108
+ /** Assemble the durable D1 receipt from a corpus scan. */
109
+ export declare function buildReceipt(corpus: Pick<PrCorpus, 'repo'>, prNumber: number, headSha: string, scan: PrScanResult, now?: Date): AutoCloseReceipt;
110
+ export type ReconcileStatus = 'clean' | 'anomaly' | 'missing-receipt' | 'ambiguous-receipt' | 'unexpected-body';
111
+ export interface ReconcileResult {
112
+ status: ReconcileStatus;
113
+ /** Normalized keys found in the merged commit message. */
114
+ findings: string[];
115
+ /** Findings not covered by the receipt's authorizing set (the anomaly set). */
116
+ undeclared: string[];
117
+ /**
118
+ * OBSERVATION MODE: the issues an armed enforcer WOULD reopen. Reported for the
119
+ * audit trail; D2 never acts on it (no auto-reopen until controls pass).
120
+ */
121
+ reopenCandidates: string[];
122
+ /**
123
+ * Whether the merged commit carried a non-empty BODY after RFC-822 trailer
124
+ * lines are stripped (mmnto-ai/totem#1762 addendum + the 0330Z trailer-strip
125
+ * fold-in). Under the E lever (squash message = BLANK) the body should be
126
+ * empty; a non-empty non-trailer body is the posture signal (`unexpected-body`).
127
+ */
128
+ bodyPresent: boolean;
129
+ /** Precise operator-facing message for the job log. */
130
+ message: string;
131
+ }
132
+ /** Options for {@link reconcile}. */
133
+ export interface ReconcileOptions {
134
+ /** `owner/repo` the merge landed on (enables same-repo key equivalence). */
135
+ repo?: string;
136
+ /** Expected PR number — a receipt for a different PR is ambiguous. */
137
+ expectedPrNumber?: number;
138
+ }
139
+ /**
140
+ * D2: reconcile the merged HEAD commit message against the D1 receipt.
141
+ * OBSERVATION MODE — every non-clean outcome ALERTS (the caller decides exit
142
+ * code); NONE reopens.
143
+ *
144
+ * - `clean` — no close-keyword-adjacent ref, or every ref is
145
+ * authorized. Quiet path (empty / trailer-only body).
146
+ * - `anomaly` — a closure-capable message with ≥1 UNAUTHORIZED ref.
147
+ * The zero-allowed-set (`declaredByMarker: []`) + a
148
+ * closure-capable message is the #2471 specimen.
149
+ * - `missing-receipt` — a closure-capable message but NO receipt (PR merged
150
+ * before D1 existed, or the artifact expired /
151
+ * could not be downloaded). Alert, never guess.
152
+ * - `ambiguous-receipt`— a closure-capable message but the receipt is malformed
153
+ * or is for a different PR. Alert, never guess.
154
+ * - `unexpected-body` — a non-empty non-trailer body under BLANK with NO
155
+ * unauthorized ref: posture-drift / `--body`-override
156
+ * evidence (no closure harm). The caller surfaces it as
157
+ * a non-failing signal (interpretation call).
158
+ */
159
+ export declare function reconcile(receipt: AutoCloseReceipt | null, mergedBody: string, opts?: ReconcileOptions): ReconcileResult;
160
+ //# sourceMappingURL=receipt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipt.d.ts","sourceRoot":"","sources":["../../src/autoclose/receipt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;OAIG;IACH,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,6DAA6D;IAC7D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,oEAAoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAUD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAY1E;AAgCD,8DAA8D;AAC9D,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uBAAuB,EAAE,eAAe,EAAE,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,iFAAiF;IACjF,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,wEAAwE;IACxE,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAOD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,GAAG,YAAY,CAsB3D;AAED,0DAA0D;AAC1D,wBAAgB,YAAY,CAC1B,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,EAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,YAAY,EAClB,GAAG,GAAE,IAAiB,GACrB,gBAAgB,CAgBlB;AAED,MAAM,MAAM,eAAe,GACvB,OAAO,GACP,SAAS,GACT,iBAAiB,GACjB,mBAAmB,GACnB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;;OAKG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAYD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAChC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,gBAAqB,GAC1B,eAAe,CAwCjB"}