create-pmos 0.15.0 → 0.16.1
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.
- package/README.md +65 -0
- package/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/workspace/README.md +50 -13
- package/workspace/bin/pmos.js +204 -51
- package/workspace/design-parity.js +358 -0
- package/workspace/doctor.js +54 -0
- package/workspace/lib/find-browser.js +60 -0
- package/workspace/lib/four-panel.js +345 -0
- package/workspace/lib/git-info.js +28 -1
- package/workspace/lib/mobile-nav.js +32 -2
- package/workspace/lib/server-discovery.js +127 -0
- package/workspace/package-lock.json +29 -2
- package/workspace/package.json +13 -5
- package/workspace/public/index.html +79 -409
- package/workspace/public/pm/app.js +242 -0
- package/workspace/public/pm/data.js +905 -0
- package/workspace/public/pm/dom.js +116 -0
- package/workspace/public/pm/fixture.js +979 -0
- package/workspace/public/pm/mobile.css +200 -0
- package/workspace/public/pm/mobile.js +403 -0
- package/workspace/public/pm/modes.js +1076 -0
- package/workspace/public/pm/panel-detail.js +648 -0
- package/workspace/public/pm/panel-initiatives.js +426 -0
- package/workspace/public/pm/panel-repos.js +553 -0
- package/workspace/public/pm/panel-tabs.js +487 -0
- package/workspace/public/pm/state.js +168 -0
- package/workspace/public/pm/tab-doc.js +279 -0
- package/workspace/public/pm/tab-task.js +83 -0
- package/workspace/public/pm/tab-term.js +560 -0
- package/workspace/public/pm/tokens.css +73 -0
- package/workspace/public/vendor/ibm-plex-mono-500.woff2 +0 -0
- package/workspace/server.js +174 -11
- package/workspace/focus.test.js +0 -650
- package/workspace/handoff.test.js +0 -443
- package/workspace/profiles.test.js +0 -362
- package/workspace/public/app.js +0 -5560
- package/workspace/public/styles.css +0 -2258
- package/workspace/repo-scan.test.js +0 -327
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* Unit suite for lib/handoff.js — the handoff links model.
|
|
3
|
-
*
|
|
4
|
-
* No framework, no mocks. Records are REAL markdown files on disk, parsed by the REAL
|
|
5
|
-
* `parseLeadingFrontmatter` (lib/file-mode-parse.js) — the same function the server and the kit's
|
|
6
|
-
* metrics.py agree on. A test double for the parser would agree with whatever this file assumed,
|
|
7
|
-
* and the frontmatter format's sharp edge (section A5) is precisely a thing a double would hide.
|
|
8
|
-
*
|
|
9
|
-
* The suite is organised around the one invariant the module exists to hold:
|
|
10
|
-
*
|
|
11
|
-
* A · the five record fields, through the real parser — and the format's truncation trap
|
|
12
|
-
* B · every link is NOT-CONNECTED, per row, naming its own system
|
|
13
|
-
* C · absence and emptiness are stated, never implied
|
|
14
|
-
* D · a malformed declaration is a problem with its value verbatim — never dropped, never rendered
|
|
15
|
-
* E · the URL admission table: clickable only as an absolute http(s) URL
|
|
16
|
-
* F · STRUCTURAL IMPOSSIBILITY — the section that matters
|
|
17
|
-
* G · the real repository, as a corpus check
|
|
18
|
-
*
|
|
19
|
-
* Section F is the point of this file. Anyone can write a test showing that today's code did not
|
|
20
|
-
* fabricate a story count; that test passes again the day after someone adds one somewhere else.
|
|
21
|
-
* F asserts that the module CANNOT: it imports nothing, so no reading can enter it; it returns no
|
|
22
|
-
* number anywhere, so a count has no field to live in; its key sets are compared for equality, so a
|
|
23
|
-
* `synced_at` cannot be added quietly; its result is frozen, so `connected` cannot be flipped
|
|
24
|
-
* downstream; and every string it emits is either copied verbatim out of the record file or is one
|
|
25
|
-
* of ten enumerated fixed sentences.
|
|
26
|
-
*
|
|
27
|
-
* Exits non-zero if any check fails.
|
|
28
|
-
*/
|
|
29
|
-
'use strict';
|
|
30
|
-
const fs = require('fs');
|
|
31
|
-
const os = require('os');
|
|
32
|
-
const path = require('path');
|
|
33
|
-
const assert = require('assert');
|
|
34
|
-
|
|
35
|
-
const H = require('./lib/handoff');
|
|
36
|
-
const { parseLeadingFrontmatter } = require('./lib/file-mode-parse');
|
|
37
|
-
|
|
38
|
-
const results = [];
|
|
39
|
-
const ok = (name, cond, extra = '') => { results.push({ name, cond }); console.log(`${cond ? '✓' : '✗'} ${name}${extra ? ' — ' + extra : ''}`); };
|
|
40
|
-
|
|
41
|
-
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'pmos-handoff-'));
|
|
42
|
-
/** An initiative record file with whatever frontmatter the case needs. */
|
|
43
|
-
function record(fm, body = 'Body prose, which must never be able to fake a declaration.\n') {
|
|
44
|
-
return `---\n${Object.entries(fm).map(([k, v]) => `${k}: ${v}`).join('\n')}\n---\n\n${body}`;
|
|
45
|
-
}
|
|
46
|
-
function writeRecord(name, fm, body) {
|
|
47
|
-
const full = path.join(TMP, name);
|
|
48
|
-
fs.writeFileSync(full, record(fm, body));
|
|
49
|
-
return full;
|
|
50
|
-
}
|
|
51
|
-
/** The whole path a real record takes: file → real parser → model. */
|
|
52
|
-
const modelOf = (file) => H.readLinks(parseLeadingFrontmatter(fs.readFileSync(file, 'utf8')));
|
|
53
|
-
const linkById = (m, id) => m.links.find((l) => l.id === id) || null;
|
|
54
|
-
const problemById = (m, id) => m.problems.find((p) => p.id === id) || null;
|
|
55
|
-
|
|
56
|
-
/** Every string anywhere in a value — the basis of the whitelist and the fabrication checks. */
|
|
57
|
-
function collectStrings(v, out = []) {
|
|
58
|
-
if (typeof v === 'string') out.push(v);
|
|
59
|
-
else if (Array.isArray(v)) v.forEach((x) => collectStrings(x, out));
|
|
60
|
-
else if (v && typeof v === 'object') Object.keys(v).forEach((k) => collectStrings(v[k], out));
|
|
61
|
-
return out;
|
|
62
|
-
}
|
|
63
|
-
/** Every non-string primitive anywhere — used to prove there is not one number in the model. */
|
|
64
|
-
function collectPrimitives(v, out = []) {
|
|
65
|
-
if (v === null || typeof v !== 'object') out.push(v);
|
|
66
|
-
else if (Array.isArray(v)) v.forEach((x) => collectPrimitives(x, out));
|
|
67
|
-
else Object.keys(v).forEach((k) => collectPrimitives(v[k], out));
|
|
68
|
-
return out;
|
|
69
|
-
}
|
|
70
|
-
const uniqSorted = (a) => [...new Set(a)].sort();
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
// ── A · the five record fields, through the real parser (R1) ────────────────────────────────
|
|
74
|
-
const allFile = writeRecord('all-five.md', {
|
|
75
|
-
type: 'Initiative', title: '"crm import wizard"', timestamp: '2026-07-20',
|
|
76
|
-
id: 'crm-import-wizard', lane: 'feature', parent_kr: 'q3-2026:o1:kr2',
|
|
77
|
-
flow: 'adlc-handoff',
|
|
78
|
-
handoff_to: 'atlas app team — the ATL board',
|
|
79
|
-
handoff_epic: 'ATL-482',
|
|
80
|
-
handoff_page: 'https://example.atlassian.net/wiki/spaces/PM/crm-import-wizard-prd',
|
|
81
|
-
handoff_at: '2026-07-20',
|
|
82
|
-
});
|
|
83
|
-
const allText = fs.readFileSync(allFile, 'utf8');
|
|
84
|
-
const all = modelOf(allFile);
|
|
85
|
-
|
|
86
|
-
ok('A1: the five R1 keys round-trip through the REAL parser into the model',
|
|
87
|
-
all.handoff === true && all.flow === 'adlc-handoff' && all.to === 'atlas app team — the ATL board'
|
|
88
|
-
&& all.at === '2026-07-20' && all.links.length === 2,
|
|
89
|
-
JSON.stringify({ flow: all.flow, at: all.at, links: all.links.length }));
|
|
90
|
-
|
|
91
|
-
ok('A2: every declared value is carried VERBATIM — nothing is normalised, prettified or re-cased',
|
|
92
|
-
linkById(all, 'handoff_epic').declared === 'ATL-482'
|
|
93
|
-
&& linkById(all, 'handoff_page').declared === 'https://example.atlassian.net/wiki/spaces/PM/crm-import-wizard-prd',
|
|
94
|
-
linkById(all, 'handoff_epic').declared);
|
|
95
|
-
|
|
96
|
-
ok('A3: `systems` names both systems, from which keys are declared — not from any reading',
|
|
97
|
-
JSON.stringify(all.systems) === JSON.stringify(['Jira', 'Confluence']), JSON.stringify(all.systems));
|
|
98
|
-
|
|
99
|
-
// Link order is fixed by the module, not by the record, so the section cannot re-order itself when
|
|
100
|
-
// a PM happens to type the page key above the epic key.
|
|
101
|
-
const flippedFile = writeRecord('flipped.md', {
|
|
102
|
-
type: 'Initiative', title: '"flipped"', timestamp: '2026-07-20', id: 'flipped',
|
|
103
|
-
handoff_page: 'https://example.atlassian.net/wiki/x', handoff_epic: 'ATL-9',
|
|
104
|
-
});
|
|
105
|
-
ok('A4: link order is the module\'s, not the record\'s — frontmatter order cannot reshuffle the section',
|
|
106
|
-
JSON.stringify(modelOf(flippedFile).links.map((l) => l.id)) === JSON.stringify(['handoff_epic', 'handoff_page']),
|
|
107
|
-
JSON.stringify(modelOf(flippedFile).links.map((l) => l.id)));
|
|
108
|
-
|
|
109
|
-
/* A5 — the truncation trap (PRD R2). This is here so that a later author who reaches for
|
|
110
|
-
* `links:` as a YAML list finds a FAILING TEST explaining what it costs, rather than discovering
|
|
111
|
-
* it through an initiative that silently reads as unanchored. The list does not partially parse:
|
|
112
|
-
* the `links:` line ends the frontmatter block, and every key below it — including `lane` and
|
|
113
|
-
* `parent_kr`, the two the Initiative Gate rejects work for lacking — is dropped without a word. */
|
|
114
|
-
const listFile = path.join(TMP, 'yaml-list.md');
|
|
115
|
-
fs.writeFileSync(listFile,
|
|
116
|
-
'---\ntype: Initiative\ntitle: "listy"\ntimestamp: 2026-07-29\nid: listy\n'
|
|
117
|
-
+ 'links:\n - ATL-482\n - https://example.atlassian.net/wiki/x\n'
|
|
118
|
-
+ 'lane: feature\nparent_kr: q3-2026:o1:kr2\n---\n\nBody.\n');
|
|
119
|
-
const truncated = parseLeadingFrontmatter(fs.readFileSync(listFile, 'utf8'));
|
|
120
|
-
ok('A5: a YAML list TERMINATES the frontmatter block — `lane` and `parent_kr` below it vanish, '
|
|
121
|
-
+ 'which is why the handoff fields are flat and single-line and must stay that way',
|
|
122
|
-
truncated.id === 'listy' && !('links' in truncated) && !('lane' in truncated) && !('parent_kr' in truncated),
|
|
123
|
-
JSON.stringify(Object.keys(truncated)));
|
|
124
|
-
|
|
125
|
-
// ── B · every link is NOT-CONNECTED, per row, naming its own system (R4) ────────────────────
|
|
126
|
-
const epic = linkById(all, 'handoff_epic');
|
|
127
|
-
const page = linkById(all, 'handoff_page');
|
|
128
|
-
|
|
129
|
-
ok('B1: the epic row carries the state, the clause and the full sentence — on the ROW, not once per section',
|
|
130
|
-
epic.state === 'not-connected' && epic.why === 'no Jira credential is configured'
|
|
131
|
-
&& epic.state_text === 'not connected — no Jira credential is configured; this is what the record declares, not a reading',
|
|
132
|
-
epic.state_text);
|
|
133
|
-
|
|
134
|
-
ok('B2: a Confluence row says Confluence — the state names the system this row points at, not a generic one',
|
|
135
|
-
page.state === 'not-connected' && page.why === 'no Confluence credential is configured'
|
|
136
|
-
&& page.state_text.includes('no Confluence credential is configured') && !page.state_text.includes('Jira'),
|
|
137
|
-
page.state_text);
|
|
138
|
-
|
|
139
|
-
ok('B3: the state is text, so it can be a row\'s accessible name — never colour, never a tooltip alone',
|
|
140
|
-
all.links.every((l) => typeof l.state_text === 'string' && l.state_text.length > 60
|
|
141
|
-
&& typeof l.why === 'string' && l.why.length > 20),
|
|
142
|
-
JSON.stringify(all.links.map((l) => l.state_text.length)));
|
|
143
|
-
|
|
144
|
-
ok('B4: `kind` is a marker for WHICH system, and is the only field that could ever be a badge',
|
|
145
|
-
epic.kind === 'jira' && page.kind === 'confluence', `${epic.kind} / ${page.kind}`);
|
|
146
|
-
|
|
147
|
-
ok('B5: `id` is the RECORD KEY, so a renderer keying rows on it never prints it as the epic',
|
|
148
|
-
epic.id === 'handoff_epic' && page.id === 'handoff_page' && epic.id !== epic.declared,
|
|
149
|
-
`${epic.id} vs ${epic.declared}`);
|
|
150
|
-
|
|
151
|
-
// ── C · absence and emptiness are stated, never implied ─────────────────────────────────────
|
|
152
|
-
const absent = H.readLinks(undefined);
|
|
153
|
-
ok('C1: no argument at all is a successful read of nothing — calm, not an error, not a claim',
|
|
154
|
-
absent.handoff === false && absent.links.length === 0 && absent.problems.length === 0
|
|
155
|
-
&& absent.empty === false && absent.flow === null && absent.to === null && absent.at === null,
|
|
156
|
-
JSON.stringify(absent));
|
|
157
|
-
|
|
158
|
-
ok('C2: null is answered identically — an initiative with no record is not a fault',
|
|
159
|
-
JSON.stringify(H.readLinks(null)) === JSON.stringify(absent));
|
|
160
|
-
|
|
161
|
-
const plainFile = writeRecord('plain.md', {
|
|
162
|
-
type: 'Initiative', title: '"plain"', timestamp: '2026-07-29', id: 'plain',
|
|
163
|
-
lane: 'feature', parent_kr: 'q3-2026:o1:kr2',
|
|
164
|
-
});
|
|
165
|
-
const plain = modelOf(plainFile);
|
|
166
|
-
ok('C3: a record that was never handed off claims nothing — the panel is unchanged for it',
|
|
167
|
-
plain.handoff === false && plain.empty === false && plain.links.length === 0
|
|
168
|
-
&& plain.problems.length === 0 && plain.systems.length === 0,
|
|
169
|
-
JSON.stringify({ handoff: plain.handoff, empty: plain.empty }));
|
|
170
|
-
|
|
171
|
-
const flowOnlyFile = writeRecord('flow-only.md', {
|
|
172
|
-
type: 'Initiative', title: '"flow only"', timestamp: '2026-07-29', id: 'flow-only', flow: 'adlc-handoff',
|
|
173
|
-
});
|
|
174
|
-
const flowOnly = modelOf(flowOnlyFile);
|
|
175
|
-
ok('C4: `flow:` declared with no references is a VERIFIED EMPTY (R6) — a stated absence, not a hidden section',
|
|
176
|
-
flowOnly.handoff === true && flowOnly.empty === true && flowOnly.flow === 'adlc-handoff'
|
|
177
|
-
&& flowOnly.links.length === 0 && flowOnly.problems.length === 0,
|
|
178
|
-
JSON.stringify({ handoff: flowOnly.handoff, empty: flowOnly.empty }));
|
|
179
|
-
|
|
180
|
-
const toOnlyFile = writeRecord('to-only.md', {
|
|
181
|
-
type: 'Initiative', title: '"to only"', timestamp: '2026-07-29', id: 'to-only', handoff_to: 'atlas app team',
|
|
182
|
-
});
|
|
183
|
-
ok('C5: a recipient with no references is the same verified empty — the section still renders',
|
|
184
|
-
modelOf(toOnlyFile).empty === true && modelOf(toOnlyFile).to === 'atlas app team');
|
|
185
|
-
|
|
186
|
-
ok('C6: a declared reference is NOT empty — `empty` means nothing to show, never nothing to say',
|
|
187
|
-
all.empty === false, String(all.empty));
|
|
188
|
-
|
|
189
|
-
// ── D · a malformed declaration is red, with its value verbatim (R7) ────────────────────────
|
|
190
|
-
const badFile = writeRecord('malformed.md', {
|
|
191
|
-
type: 'Initiative', title: '"malformed"', timestamp: '2026-07-29', id: 'malformed',
|
|
192
|
-
flow: '../../../etc/passwd',
|
|
193
|
-
handoff_epic: 'ATL-482 import wizard epic 3 of 7 done',
|
|
194
|
-
handoff_at: 'soonish',
|
|
195
|
-
});
|
|
196
|
-
const bad = modelOf(badFile);
|
|
197
|
-
|
|
198
|
-
ok('D1: `handoff_at: soonish` is a problem with the offending value VERBATIM, and never reaches `at`',
|
|
199
|
-
bad.at === null && !!problemById(bad, 'handoff_at') && problemById(bad, 'handoff_at').declared === 'soonish'
|
|
200
|
-
&& problemById(bad, 'handoff_at').why.includes('will not guess'),
|
|
201
|
-
JSON.stringify(problemById(bad, 'handoff_at')));
|
|
202
|
-
|
|
203
|
-
ok('D2: an epic carrying whitespace-separated prose is a problem, NOT a link — junk is never dressed as a reference',
|
|
204
|
-
bad.links.length === 0 && problemById(bad, 'handoff_epic').declared === 'ATL-482 import wizard epic 3 of 7 done',
|
|
205
|
-
JSON.stringify(bad.links.map((l) => l.declared)));
|
|
206
|
-
|
|
207
|
-
ok('D3: a `flow:` that is a path rather than a template name is refused — the "open the file" affordance stays honest',
|
|
208
|
-
!!problemById(bad, 'flow') && problemById(bad, 'flow').declared === '../../../etc/passwd' && bad.flow === null,
|
|
209
|
-
JSON.stringify(problemById(bad, 'flow')));
|
|
210
|
-
|
|
211
|
-
ok('D4: a malformed reference still names its system, so the ladder can still say what PMOS cannot read',
|
|
212
|
-
JSON.stringify(bad.systems) === JSON.stringify(['Jira']), JSON.stringify(bad.systems));
|
|
213
|
-
|
|
214
|
-
ok('D5: a record that is only malformed is NOT a verified empty — red wins over calm',
|
|
215
|
-
bad.handoff === true && bad.empty === false && bad.problems.length === 3,
|
|
216
|
-
JSON.stringify({ empty: bad.empty, problems: bad.problems.length }));
|
|
217
|
-
|
|
218
|
-
const emptyVal = H.readLinks({ handoff_epic: ' ', handoff_to: '' });
|
|
219
|
-
ok('D6: present-but-empty is reported as present-and-empty, not as absent',
|
|
220
|
-
emptyVal.problems.length === 2 && emptyVal.problems.every((p) => p.why.includes('present but empty'))
|
|
221
|
-
&& emptyVal.problems.every((p) => p.declared === null),
|
|
222
|
-
JSON.stringify(emptyVal.problems.map((p) => p.id)));
|
|
223
|
-
|
|
224
|
-
const nonString = H.readLinks({ handoff_at: 20260720, handoff_epic: ['ATL-1'], handoff_to: { team: 'atlas' } });
|
|
225
|
-
ok('D7: a value that was never text yields a problem naming the TYPE and a null value — nothing is invented to print',
|
|
226
|
-
nonString.problems.length === 3 && nonString.problems.every((p) => p.declared === null)
|
|
227
|
-
&& problemById(nonString, 'handoff_at').why.includes('a number')
|
|
228
|
-
&& problemById(nonString, 'handoff_epic').why.includes('an array'),
|
|
229
|
-
JSON.stringify(nonString.problems.map((p) => `${p.id}=${p.declared}`)));
|
|
230
|
-
|
|
231
|
-
const ctrl = H.readLinks({ handoff_epic: 'ATL-\u0007482' });
|
|
232
|
-
ok('D8: a control character is refused rather than rendered — what it would print is not what the record shows',
|
|
233
|
-
ctrl.links.length === 0 && !!problemById(ctrl, 'handoff_epic')
|
|
234
|
-
&& problemById(ctrl, 'handoff_epic').why.includes('control character'));
|
|
235
|
-
|
|
236
|
-
const wrongShape = H.readLinks(allText);
|
|
237
|
-
ok('D9: handed the record TEXT instead of its parsed frontmatter, the model states a fault — not a silent empty',
|
|
238
|
-
wrongShape.handoff === false && wrongShape.problems.length === 1
|
|
239
|
-
&& wrongShape.problems[0].id === 'record' && wrongShape.problems[0].why.includes('a string'),
|
|
240
|
-
wrongShape.problems[0].why.slice(0, 60));
|
|
241
|
-
|
|
242
|
-
ok('D10: a problem carries exactly PROBLEM_FIELDS — no room for a "last checked" beside the reason',
|
|
243
|
-
[bad, emptyVal, nonString, wrongShape].every((m) => m.problems.every(
|
|
244
|
-
(p) => JSON.stringify(Object.keys(p)) === JSON.stringify(H.PROBLEM_FIELDS))),
|
|
245
|
-
JSON.stringify(H.PROBLEM_FIELDS));
|
|
246
|
-
|
|
247
|
-
ok('D11: no problem message embeds the offending value — the value is printed once, by `declared`',
|
|
248
|
-
problemById(bad, 'handoff_at').why.indexOf('soonish') === -1
|
|
249
|
-
&& problemById(bad, 'flow').why.indexOf('passwd') === -1,
|
|
250
|
-
problemById(bad, 'handoff_at').why.slice(0, 50));
|
|
251
|
-
|
|
252
|
-
// ── E · the URL admission table (R5, PRD test 3) ────────────────────────────────────────────
|
|
253
|
-
const TABLE = [
|
|
254
|
-
['https://example.atlassian.net/browse/ATL-482', true, 'an absolute https URL'],
|
|
255
|
-
['http://jira.internal/browse/ATL-482', true, 'an absolute http URL'],
|
|
256
|
-
['HTTPS://EXAMPLE.ATLASSIAN.NET/browse/ATL-482', true, 'scheme case does not matter'],
|
|
257
|
-
['ATL-482', false, 'a bare reference'],
|
|
258
|
-
['jira://ATL-482', false, 'a non-http scheme'],
|
|
259
|
-
['//evil.example.com/browse/ATL-482', false, 'protocol-relative — inherits whatever scheme the page is on'],
|
|
260
|
-
['javascript:alert(1)', false, 'a script URL'],
|
|
261
|
-
['data:text/html,<h1>hi</h1>', false, 'a data URL'],
|
|
262
|
-
['/planning/prd/thing.md', false, 'a repo-relative path'],
|
|
263
|
-
['https://', false, 'a scheme with no host'],
|
|
264
|
-
];
|
|
265
|
-
const admitted = TABLE.map(([v, want]) => {
|
|
266
|
-
const l = H.readLinks({ handoff_epic: v }).links[0];
|
|
267
|
-
return !!(l && l.href) === want && (l ? (want ? l.href === v : l.href === null) : false);
|
|
268
|
-
});
|
|
269
|
-
ok('E1: the admission table holds — a value is clickable only as an absolute http(s) URL',
|
|
270
|
-
admitted.every(Boolean),
|
|
271
|
-
TABLE.filter((_, i) => !admitted[i]).map(([v]) => v).join(' | ') || 'all 10 rows as specified');
|
|
272
|
-
|
|
273
|
-
const dangerous = ['javascript:alert(1)', 'data:text/html,<h1>hi</h1>', '//evil.example.com'];
|
|
274
|
-
ok('E2: a `javascript:`/`data:`/protocol-relative value NEVER reaches an href — it survives only as `declared`',
|
|
275
|
-
dangerous.every((v) => {
|
|
276
|
-
const l = H.readLinks({ handoff_epic: v }).links[0];
|
|
277
|
-
return l.href === null && l.declared === v && collectStrings(l).filter((s) => s === v).length === 1;
|
|
278
|
-
}));
|
|
279
|
-
|
|
280
|
-
const inert = H.readLinks({ handoff_epic: 'ATL-482' }).links[0];
|
|
281
|
-
ok('E3: a refused value is inert WITH ITS REASON — never silently dropped, never silently dead',
|
|
282
|
-
inert.href === null && inert.inert_why === H.INERT_WHY && inert.declared === 'ATL-482'
|
|
283
|
-
&& inert.inert_why.includes('will not construct one it was not given'),
|
|
284
|
-
inert.inert_why);
|
|
285
|
-
|
|
286
|
-
ok('E4: an admitted href is byte-identical to the declared value — PMOS links what it was given, nothing else',
|
|
287
|
-
page.href === page.declared && page.inert_why === null, page.href);
|
|
288
|
-
|
|
289
|
-
const rawish = H.readLinks({ handoff_page: 'https://x.example/<script>&"' });
|
|
290
|
-
ok('E5: values are returned UNESCAPED and verbatim — escaping belongs at the interpolation site, and '
|
|
291
|
-
+ 'a model that pre-escaped could not be checked against the record file',
|
|
292
|
-
rawish.links[0].declared === 'https://x.example/<script>&"'
|
|
293
|
-
&& rawish.links[0].href === 'https://x.example/<script>&"',
|
|
294
|
-
rawish.links[0].declared);
|
|
295
|
-
|
|
296
|
-
// ── F · STRUCTURAL IMPOSSIBILITY ────────────────────────────────────────────────────────────
|
|
297
|
-
// A corpus wide enough that the properties below are claims about the module, not about one case.
|
|
298
|
-
const CORPUS = [
|
|
299
|
-
['absent', undefined], ['null', null], ['plain', parseLeadingFrontmatter(fs.readFileSync(plainFile, 'utf8'))],
|
|
300
|
-
['all five', parseLeadingFrontmatter(allText)], ['flow only', { flow: 'adlc-handoff' }],
|
|
301
|
-
['malformed', parseLeadingFrontmatter(fs.readFileSync(badFile, 'utf8'))],
|
|
302
|
-
['empty values', { handoff_epic: ' ', handoff_to: '' }],
|
|
303
|
-
['non-string values', { handoff_at: 20260720, handoff_epic: ['ATL-1'] }],
|
|
304
|
-
['wrong shape', allText], ['an array', ['handoff_epic']], ['a number', 7], ['a function', () => 1],
|
|
305
|
-
// The hostile record: a PM (or a well-meaning agent) writes into the record the very things PMOS
|
|
306
|
-
// cannot know. None of it may reach the model, because none of it is a field the model has.
|
|
307
|
-
['hostile', {
|
|
308
|
-
flow: 'adlc-handoff', handoff_epic: 'ATL-482', handoff_to: 'atlas app team', handoff_at: '2026-07-20',
|
|
309
|
-
connected: 'true', story_count: '7', stories_done: '3 of 7', synced_at: '12:38',
|
|
310
|
-
last_checked: '2026-07-29T12:38:00Z', jira_status: 'In Progress', progress: '43%',
|
|
311
|
-
}],
|
|
312
|
-
...TABLE.map(([v]) => [`url:${v}`, { handoff_epic: v }]),
|
|
313
|
-
];
|
|
314
|
-
const MODELS = CORPUS.map(([name, input]) => [name, H.readLinks(input)]);
|
|
315
|
-
|
|
316
|
-
const src = fs.readFileSync(path.join(__dirname, 'lib', 'handoff.js'), 'utf8');
|
|
317
|
-
ok('F1: the module imports NOTHING — with no `require(` there is no fs, no socket and no clock, '
|
|
318
|
-
+ 'so there is no path by which a reading of an external system could enter it',
|
|
319
|
-
!/\brequire\s*\(/.test(src) && !/\bimport\s*\(/.test(src) && !/^\s*import\s/m.test(src),
|
|
320
|
-
(src.match(/\brequire\s*\([^)]*\)/g) || []).join(', ') || 'no imports of any kind');
|
|
321
|
-
|
|
322
|
-
const CLOCK_OR_IO = [/\bDate\b/, /\bMath\b/, /\bprocess\b/, /\bfetch\s*\(/, /\bsetTimeout\b/,
|
|
323
|
-
/\bsetInterval\b/, /\bXMLHttpRequest\b/, /\bWebSocket\b/, /\bglobalThis\b/];
|
|
324
|
-
ok('F2: no clock and no I/O identifier appears in the source — a sync time cannot be formatted by '
|
|
325
|
-
+ 'code that cannot read the time',
|
|
326
|
-
CLOCK_OR_IO.every((re) => !re.test(src)),
|
|
327
|
-
CLOCK_OR_IO.filter((re) => re.test(src)).map(String).join(', ') || 'none present');
|
|
328
|
-
|
|
329
|
-
const numbers = MODELS.flatMap(([name, m]) => collectPrimitives(m).filter((p) => typeof p === 'number').map((p) => `${name}:${p}`));
|
|
330
|
-
ok('F3: there is not ONE number anywhere in the model, for any input — a story count, an issue '
|
|
331
|
-
+ 'tally and a progress percentage have no field they could occupy',
|
|
332
|
-
numbers.length === 0, numbers.join(', ') || `${MODELS.length} inputs, 0 numeric values`);
|
|
333
|
-
|
|
334
|
-
const exportedOutputs = [H.whyFor('Jira'), H.stateTextFor('Confluence'), H.NOT_CONNECTED, H.INERT_WHY,
|
|
335
|
-
H.HANDOFF_KEYS, H.REFERENCE_KEYS, H.LINK_FIELDS, H.PROBLEM_FIELDS, H.RESULT_FIELDS];
|
|
336
|
-
ok('F4: no export returns a number about anything, external or otherwise',
|
|
337
|
-
exportedOutputs.every((v) => collectPrimitives(v).every((p) => typeof p !== 'number')),
|
|
338
|
-
Object.keys(H).join(', '));
|
|
339
|
-
|
|
340
|
-
const linkKeys = MODELS.flatMap(([name, m]) => m.links.map((l) => [name, JSON.stringify(Object.keys(l))]));
|
|
341
|
-
ok('F5: every link carries EXACTLY LINK_FIELDS, in order — a `story_count` or a `synced_at` cannot '
|
|
342
|
-
+ 'be added to a row without failing here',
|
|
343
|
-
linkKeys.length > 0 && linkKeys.every(([, k]) => k === JSON.stringify(H.LINK_FIELDS)),
|
|
344
|
-
`${linkKeys.length} links checked against ${JSON.stringify(H.LINK_FIELDS)}`);
|
|
345
|
-
|
|
346
|
-
ok('F6: every result carries EXACTLY RESULT_FIELDS, in order — the same lock on the model itself',
|
|
347
|
-
MODELS.every(([, m]) => JSON.stringify(Object.keys(m)) === JSON.stringify(H.RESULT_FIELDS)),
|
|
348
|
-
JSON.stringify(H.RESULT_FIELDS));
|
|
349
|
-
|
|
350
|
-
ok('F7: every link in every model is `not-connected` — there is no second state and no input reaches one',
|
|
351
|
-
MODELS.every(([, m]) => m.links.every((l) => l.state === H.NOT_CONNECTED && l.state === 'not-connected')),
|
|
352
|
-
`${MODELS.reduce((n, [, m]) => n + m.links.length, 0)} links, one state`);
|
|
353
|
-
|
|
354
|
-
ok('F8: `connected` is false for every input — including a record that flatly declares it true',
|
|
355
|
-
MODELS.every(([, m]) => m.connected === false),
|
|
356
|
-
`${MODELS.length} inputs`);
|
|
357
|
-
|
|
358
|
-
// Frozen, so a consumer that would rather the answer were different cannot make it so after the
|
|
359
|
-
// fact. In strict mode the assignment throws; the assertion is on the value either way.
|
|
360
|
-
const frozen = MODELS.find(([n]) => n === 'all five')[1];
|
|
361
|
-
let threw = 0;
|
|
362
|
-
try { frozen.connected = true; } catch { threw++; }
|
|
363
|
-
try { frozen.synced_at = '12:38'; } catch { threw++; }
|
|
364
|
-
try { frozen.links[0].story_count = '3 of 7'; } catch { threw++; }
|
|
365
|
-
try { frozen.links.push({ kind: 'jira' }); } catch { threw++; }
|
|
366
|
-
ok('F9: the model is deep-frozen — `connected` cannot be flipped and no fabricated field can be '
|
|
367
|
-
+ 'bolted on downstream',
|
|
368
|
-
threw === 4 && frozen.connected === false && !('synced_at' in frozen)
|
|
369
|
-
&& !('story_count' in frozen.links[0]) && frozen.links.length === 2,
|
|
370
|
-
`${threw}/4 mutations refused`);
|
|
371
|
-
|
|
372
|
-
/* F10 — THE ANTI-FABRICATION WHITELIST (PRD test 8), and the check the rest of this file exists
|
|
373
|
-
* to support. Not a denylist of "3 of 7" and "synced": those ban today's fabrication and license
|
|
374
|
-
* tomorrow's. Every string the model emits for a real record is partitioned against THE RECORD
|
|
375
|
-
* FILE ITSELF. Whatever is not in the file must be one of the fixed sentences enumerated below —
|
|
376
|
-
* compared for EQUALITY, so a value that is computed, estimated, tallied or formatted relative to
|
|
377
|
-
* now lands in that pile and fails immediately, whatever it is and however plausible it looks. */
|
|
378
|
-
const FIXED_VOCABULARY = [
|
|
379
|
-
'Jira', 'Confluence', 'jira', 'confluence', 'not-connected',
|
|
380
|
-
'no Jira credential is configured',
|
|
381
|
-
'no Confluence credential is configured',
|
|
382
|
-
'not connected — no Jira credential is configured; this is what the record declares, not a reading',
|
|
383
|
-
'not connected — no Confluence credential is configured; this is what the record declares, not a reading',
|
|
384
|
-
'PMOS was given a reference, not a URL, and will not construct one it was not given',
|
|
385
|
-
];
|
|
386
|
-
const notInFile = uniqSorted(collectStrings(all).filter((s) => allText.indexOf(s) === -1));
|
|
387
|
-
ok('F10: every string the model emits is either VERBATIM from the record file or one of ten fixed '
|
|
388
|
-
+ 'sentences — a value nobody typed has nowhere to hide',
|
|
389
|
-
JSON.stringify(notInFile) === JSON.stringify(uniqSorted(FIXED_VOCABULARY)),
|
|
390
|
-
notInFile.filter((s) => FIXED_VOCABULARY.indexOf(s) === -1).map((s) => JSON.stringify(s)).join(', ')
|
|
391
|
-
|| `${collectStrings(all).length} strings, all accounted for`);
|
|
392
|
-
|
|
393
|
-
// F10 is only worth anything if it can fail. This proves the partition has teeth before it is
|
|
394
|
-
// trusted: a fabricated value put through the same computation is caught by it.
|
|
395
|
-
const fabricated = { links: [{ declared: 'ATL-482', summary: '3 of 7 stories done', synced: 'synced 12:38' }] };
|
|
396
|
-
ok('F11: the F10 partition demonstrably CATCHES a fabrication — the check has teeth, not just a pass',
|
|
397
|
-
collectStrings(fabricated).filter((s) => allText.indexOf(s) === -1).sort().join('|') === '3 of 7 stories done|synced 12:38');
|
|
398
|
-
|
|
399
|
-
const hostile = MODELS.find(([n]) => n === 'hostile')[1];
|
|
400
|
-
const hostileStrings = collectStrings(hostile).join('');
|
|
401
|
-
ok('F12: the hostile record\'s fabricated keys and values reach the model NOWHERE — belt beside '
|
|
402
|
-
+ 'F10\'s braces, and the one denylist in this file, named as one',
|
|
403
|
-
['3 of 7', '12:38', '43%', 'In Progress', 'story_count', 'synced_at', 'last_checked']
|
|
404
|
-
.every((s) => hostileStrings.indexOf(s) === -1)
|
|
405
|
-
&& hostile.connected === false && hostile.links.length === 1,
|
|
406
|
-
JSON.stringify(hostile.links.map((l) => l.declared)));
|
|
407
|
-
|
|
408
|
-
ok('F13: the model is a pure function of its input — two calls are deep-equal, so nothing in it '
|
|
409
|
-
+ 'varies with when it was called',
|
|
410
|
-
(() => { try { assert.deepStrictEqual(H.readLinks(parseLeadingFrontmatter(allText)), all); return true; } catch { return false; } })());
|
|
411
|
-
|
|
412
|
-
ok('F14: no export name suggests an action or a reading PMOS cannot perform',
|
|
413
|
-
Object.keys(H).every((k) => !/sync|refresh|fetch|poll|count|progress|connect(?!ed)/i.test(k)),
|
|
414
|
-
Object.keys(H).join(', '));
|
|
415
|
-
|
|
416
|
-
// ── G · the real repository, as a corpus check ──────────────────────────────────────────────
|
|
417
|
-
// The fixtures above were written by this file and agree with it by construction. These records
|
|
418
|
-
// were written by people who had never heard of this module.
|
|
419
|
-
const initDir = path.join(__dirname, '..', 'planning', 'initiatives');
|
|
420
|
-
const realFiles = fs.readdirSync(initDir).filter((f) => f.endsWith('.md') && f !== 'index.md' && f !== 'log.md')
|
|
421
|
-
.map((f) => path.join(initDir, f));
|
|
422
|
-
const realModels = realFiles.map((f) => [path.basename(f), modelOf(f)]);
|
|
423
|
-
|
|
424
|
-
ok('G1: every real initiative record yields a well-shaped model — no throw, no missing field',
|
|
425
|
-
realModels.length > 50 && realModels.every(([, m]) => JSON.stringify(Object.keys(m)) === JSON.stringify(H.RESULT_FIELDS)),
|
|
426
|
-
`${realModels.length} records`);
|
|
427
|
-
|
|
428
|
-
ok('G2: not one of them declares a handoff today, so the panel is UNCHANGED for every initiative '
|
|
429
|
-
+ 'in this repository — the feature is additive or it is nothing',
|
|
430
|
-
realModels.every(([, m]) => m.handoff === false && m.links.length === 0 && m.problems.length === 0 && m.empty === false),
|
|
431
|
-
realModels.filter(([, m]) => m.handoff).map(([n]) => n).join(', ') || 'none handed off');
|
|
432
|
-
|
|
433
|
-
ok('G3: on the real corpus there is still no number, no connection and no unexpected field',
|
|
434
|
-
realModels.every(([, m]) => m.connected === false && collectPrimitives(m).every((p) => typeof p !== 'number')),
|
|
435
|
-
`${realModels.length} records, 0 numeric values`);
|
|
436
|
-
} catch (e) {
|
|
437
|
-
ok('no exceptions during suite', false, e && e.stack ? e.stack.split('\n').slice(0, 2).join(' @ ') : String(e));
|
|
438
|
-
} finally {
|
|
439
|
-
try { fs.rmSync(TMP, { recursive: true, force: true }); } catch {}
|
|
440
|
-
const failed = results.filter((r) => !r.cond);
|
|
441
|
-
console.log(`\n${results.length - failed.length}/${results.length} passed${failed.length ? ' — FAILURES: ' + failed.map((f) => f.name).join('; ') : ''}`);
|
|
442
|
-
process.exit(failed.length ? 1 : 0);
|
|
443
|
-
}
|