@n8r/surface 0.1.0-experimental.2 → 0.1.0-experimental.5

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 (118) hide show
  1. package/README.md +30 -4
  2. package/dist/cli.js +174 -22
  3. package/dist/codec.d.ts +2 -0
  4. package/dist/codec.js +22 -7
  5. package/dist/codex.d.ts +1 -1
  6. package/dist/codex.js +26 -10
  7. package/dist/contracts.d.ts +108 -2
  8. package/dist/cyclonedx-schemas/bom-1.7.schema.json +6700 -0
  9. package/dist/cyclonedx-schemas/cryptography-defs.schema.json +576 -0
  10. package/dist/cyclonedx-schemas/jsf-0.82.schema.json +240 -0
  11. package/dist/cyclonedx-schemas/spdx.schema.json +786 -0
  12. package/dist/cyclonedx-validation.d.ts +1 -0
  13. package/dist/cyclonedx-validation.js +45 -0
  14. package/dist/html.js +27 -3
  15. package/dist/recent.d.ts +9 -0
  16. package/dist/recent.js +71 -0
  17. package/dist/revisions.js +42 -0
  18. package/dist/skill-artifacts-cli.d.ts +1 -0
  19. package/dist/skill-artifacts-cli.js +13 -0
  20. package/dist/skill-artifacts.d.ts +15 -0
  21. package/dist/skill-artifacts.js +184 -0
  22. package/dist/skill-cyclonedx.d.ts +26 -0
  23. package/dist/skill-cyclonedx.js +214 -0
  24. package/dist/skill-presentation.d.ts +10 -0
  25. package/dist/skill-presentation.js +108 -0
  26. package/dist/skill-surface-cli.d.ts +1 -0
  27. package/dist/skill-surface-cli.js +37 -0
  28. package/dist/skill-surface-store.d.ts +7 -0
  29. package/dist/skill-surface-store.js +126 -0
  30. package/dist/surface-bom.d.ts +73 -0
  31. package/dist/surface-bom.js +139 -0
  32. package/dist/surface-draft.d.ts +6 -3
  33. package/dist/surface-draft.js +20 -3
  34. package/dist/surface-instructions.d.ts +2 -1
  35. package/dist/surface-instructions.js +10 -1
  36. package/dist/surface-overview.d.ts +15 -0
  37. package/dist/surface-overview.js +86 -0
  38. package/dist/surface-store.d.ts +5 -7
  39. package/dist/surface-store.js +34 -2
  40. package/dist/surface-view.d.ts +3 -0
  41. package/dist/surface-view.js +50 -0
  42. package/dist/validate.js +298 -0
  43. package/docs/n8r-surface/cli-bundles.md +72 -0
  44. package/docs/n8r-surface/command-outcomes.md +151 -4
  45. package/docs/n8r-surface/contracts.md +10 -0
  46. package/docs/n8r-surface/critical-path.md +21 -0
  47. package/docs/n8r-surface/first-use-access.md +143 -0
  48. package/docs/n8r-surface/first-use-overview.md +262 -0
  49. package/docs/n8r-surface/getting-started.md +55 -88
  50. package/docs/n8r-surface/guided-journey-verification.md +137 -0
  51. package/docs/n8r-surface/implementation-decisions.md +14 -14
  52. package/docs/n8r-surface/instance-first-research.md +209 -0
  53. package/docs/n8r-surface/npm-distribution.md +39 -69
  54. package/docs/n8r-surface/npm-release.md +141 -14
  55. package/docs/n8r-surface/product-direction.md +327 -0
  56. package/docs/n8r-surface/researcher-skill.md +105 -51
  57. package/docs/n8r-surface/security-assurance-strategy.md +267 -0
  58. package/docs/n8r-surface/skill-bom-verification.md +142 -0
  59. package/docs/n8r-surface/skill-cyclonedx-verification.md +144 -0
  60. package/docs/n8r-surface/skill-cyclonedx.md +85 -0
  61. package/docs/n8r-surface/skill-manual.md +129 -0
  62. package/docs/n8r-surface/skill-surface-artifacts.md +234 -0
  63. package/docs/n8r-surface/skill-usefulness-verification.md +135 -0
  64. package/docs/n8r-surface/skill-workspace-automation.md +359 -0
  65. package/docs/n8r-surface/surface-description-contract.md +224 -0
  66. package/docs/n8r-surface/surface-descriptions.md +173 -0
  67. package/docs/n8r-surface/surface-relationships.md +231 -0
  68. package/docs/n8r-surface/tester-candidate.md +158 -0
  69. package/docs/n8r-surface/versioning.md +55 -66
  70. package/docs/n8r-surface/workspace-human-check.md +101 -0
  71. package/docs/n8r-surface/workspace-installation.md +68 -0
  72. package/docs/n8r-surface/workspace-verification.md +350 -0
  73. package/docs/n8r-surface/workspace.md +83 -0
  74. package/examples/researcher/README.md +8 -0
  75. package/examples/researcher/cyclonedx/configured/README.md +7 -0
  76. package/examples/researcher/cyclonedx/configured/manifest.json +10 -0
  77. package/examples/researcher/cyclonedx/configured/mapping.json +323 -0
  78. package/examples/researcher/cyclonedx/configured/surface-records.json +1275 -0
  79. package/examples/researcher/cyclonedx/configured/surface.cdx.json +79 -0
  80. package/examples/researcher/descriptions/README.md +29 -0
  81. package/examples/researcher/descriptions/bound-r1.json +141 -0
  82. package/examples/researcher/descriptions/bound-r2.json +153 -0
  83. package/examples/researcher/descriptions/capability.json +167 -0
  84. package/examples/researcher/descriptions/research-r1.json +153 -0
  85. package/examples/researcher/descriptions/research-r2.json +157 -0
  86. package/examples/researcher/descriptions/sources.md +24 -0
  87. package/examples/researcher/relationships/README.md +34 -0
  88. package/examples/researcher/relationships/bound-r1.json +346 -0
  89. package/examples/researcher/relationships/bound-r2.json +358 -0
  90. package/examples/researcher/relationships/capability.json +325 -0
  91. package/examples/researcher/relationships/config/assistant.env.example +4 -0
  92. package/examples/researcher/relationships/research-r1.json +300 -0
  93. package/examples/researcher/relationships/research-r2.json +304 -0
  94. package/examples/researcher/relationships/sources.md +38 -0
  95. package/examples/researcher/surface-bom/README.md +17 -0
  96. package/examples/researcher/surface-bom/initial.cdx.json +188 -0
  97. package/package.json +1 -1
  98. package/plugins/n8r-surface/.codex-plugin/plugin.json +2 -2
  99. package/plugins/n8r-surface/LICENSING.md +3 -0
  100. package/plugins/n8r-surface/plugin.json +2 -2
  101. package/plugins/n8r-surface/skills/research/SKILL.md +79 -38
  102. package/plugins/n8r-surface/skills/research/references/artifacts.md +152 -0
  103. package/plugins/n8r-surface/skills/research/references/cyclonedx.md +207 -0
  104. package/plugins/n8r-surface/skills/research/references/discovery-map.md +20 -2
  105. package/plugins/n8r-surface/skills/research/references/examples/bound-r1.json +346 -0
  106. package/plugins/n8r-surface/skills/research/references/examples/capability.json +325 -0
  107. package/plugins/n8r-surface/skills/research/references/examples/research-r1.json +300 -0
  108. package/plugins/n8r-surface/skills/research/references/examples/surface.cdx.json +188 -0
  109. package/plugins/n8r-surface/skills/research/references/first-use-access.md +97 -0
  110. package/plugins/n8r-surface/skills/research/references/first-use-overview.md +101 -0
  111. package/plugins/n8r-surface/skills/research/references/instance-first.md +125 -0
  112. package/plugins/n8r-surface/skills/research/references/working-notes.md +6 -1
  113. package/plugins/n8r-surface/skills/research/references/workspace.md +89 -0
  114. package/plugins/n8r-surface/skills/research/scripts/THIRD-PARTY-NOTICES.txt +273 -0
  115. package/plugins/n8r-surface/skills/research/scripts/artifacts.mjs +8348 -0
  116. package/plugins/n8r-surface/skills/research/scripts/cyclonedx.mjs +28113 -0
  117. package/plugins/n8r-surface/skills/research/scripts/surface.mjs +27800 -0
  118. package/plugins/n8r-surface/skills/research/scripts/workspace.mjs +120 -0
package/dist/validate.js CHANGED
@@ -116,7 +116,22 @@ function references(ids, available, p) {
116
116
  if (!available.has(item))
117
117
  fail(p, `unresolved reference ${item}`);
118
118
  }
119
+ // Inspect only a data descriptor; never invoke an untrusted getter to dispatch.
120
+ function isDescription(input) {
121
+ return input !== null && typeof input === 'object' &&
122
+ ['n8r.surface/0.2', 'n8r.surface/0.3'].includes(Object.getOwnPropertyDescriptor(input, 'contract')?.value);
123
+ }
119
124
  export function validateArtefact(input) {
125
+ try {
126
+ return validate(input);
127
+ }
128
+ catch (error) {
129
+ if (isDescription(input))
130
+ fail('$description', 'invalid structure, identity, state or reference');
131
+ throw error;
132
+ }
133
+ }
134
+ function validate(input) {
120
135
  canonical(input); // checks portable values, depth and logical size before field access
121
136
  const data = object(input, '$');
122
137
  if (data.contract === 'n8r.research/0.1') {
@@ -154,6 +169,12 @@ export function validateArtefact(input) {
154
169
  references([e.to], nodes, e.id);
155
170
  }
156
171
  }
172
+ else if (data.contract === 'n8r.surface/0.2') {
173
+ validateDescription(input);
174
+ }
175
+ else if (data.contract === 'n8r.surface/0.3') {
176
+ validateGraph(input);
177
+ }
157
178
  else
158
179
  fail('$.contract', 'unsupported contract/version');
159
180
  const a = input;
@@ -163,9 +184,23 @@ export function validateArtefact(input) {
163
184
  }
164
185
  /** Explicit offline bundle verification. Missing external context is an error here. */
165
186
  export function verifyReferences(input, context) {
187
+ try {
188
+ verifyContext(input, context);
189
+ }
190
+ catch (error) {
191
+ if (isDescription(input))
192
+ fail('$description', 'invalid or unavailable evidence, capability or exclusive selection');
193
+ throw error;
194
+ }
195
+ }
196
+ function verifyContext(input, context) {
166
197
  const a = validateArtefact(input);
167
198
  const records = context.map(validateArtefact);
168
199
  unique(records.map(r => JSON.stringify([r.id, r.revision.id])), '$context');
200
+ if (a.contract === 'n8r.surface/0.2' || a.contract === 'n8r.surface/0.3') {
201
+ verifyDescriptionContext(a, records);
202
+ return;
203
+ }
169
204
  if (a.contract !== 'n8r.surface/0.1')
170
205
  return;
171
206
  for (const ref of a.records) {
@@ -182,3 +217,266 @@ export function verifyReferences(input, context) {
182
217
  }
183
218
  }
184
219
  }
220
+ const basis = shape({ record: id, revision: token, items: array(id, 1) });
221
+ const descriptionType = shape({ vocabulary: id, term: text });
222
+ const descriptionCommon = {
223
+ ...common, contract: oneOf('n8r.surface/0.2'), extensions: shape({}),
224
+ records: array(ref), vocabularies: array(shape({ id, version: token })),
225
+ nodes: array(shape({ id, label: text, type: descriptionType })),
226
+ coverage: array(text), gaps: array(text)
227
+ };
228
+ const capabilityShape = shape({
229
+ ...descriptionCommon, kind: oneOf('capability'),
230
+ capabilities: array(shape({ id, from: id, to: id, type: descriptionType,
231
+ state: oneOf('possible', 'unknown', 'omitted', 'redacted', 'absent', 'out-of-scope', 'conflicting'),
232
+ basis: array(basis), conditions: array(text), note: text })),
233
+ exclusiveGroups: array(shape({ id, members: array(id, 2) }))
234
+ });
235
+ const boundShape = shape({
236
+ ...descriptionCommon, kind: oneOf('bound'), capability: ref,
237
+ instance: shape({ id, label: text }),
238
+ bindings: array(shape({ id, capability: id,
239
+ state: oneOf('configured', 'unknown', 'omitted', 'redacted', 'out-of-scope', 'conflicting'),
240
+ claims: array(shape({ target: id, basis: array(basis, 1) })), decisions: array(basis), note: text }))
241
+ });
242
+ function descriptionBases(a) {
243
+ return a.kind === 'capability' ? a.capabilities.flatMap(c => c.basis) :
244
+ a.bindings.flatMap(b => [...b.decisions, ...b.claims.flatMap(c => c.basis)]);
245
+ }
246
+ function validateDescription(input) {
247
+ const data = object(input, '$');
248
+ (data.kind === 'capability' ? capabilityShape : boundShape)(input, '$');
249
+ const a = input;
250
+ const entries = a.kind === 'capability' ? [...a.capabilities, ...a.exclusiveGroups] : a.bindings;
251
+ unique([a.id, ...a.nodes.map(n => n.id), ...entries.map(e => e.id), ...(a.kind === 'bound' ? [a.instance.id] : [])], '$');
252
+ const nodes = new Set(a.nodes.map(n => n.id));
253
+ const vocab = unique(a.vocabularies.map(v => v.id), '$.vocabularies');
254
+ const records = unique(a.records.map(r => JSON.stringify([r.id, r.revision])), '$.records');
255
+ for (const n of a.nodes)
256
+ references([n.type.vocabulary], vocab, '$.nodes');
257
+ for (const b of descriptionBases(a)) {
258
+ references([JSON.stringify([b.record, b.revision])], records, '$.basis');
259
+ unique(b.items, '$.basis.items');
260
+ }
261
+ if (a.kind === 'capability') {
262
+ for (const c of a.capabilities) {
263
+ references([c.from], nodes, '$.capabilities.from');
264
+ references([c.to], nodes, '$.capabilities.to');
265
+ references([c.type.vocabulary], vocab, '$.capabilities.type');
266
+ if (['possible', 'absent', 'conflicting'].includes(c.state) && !c.basis.length)
267
+ fail('$', 'evidence required');
268
+ }
269
+ for (const group of a.exclusiveGroups)
270
+ references(group.members, new Set(a.capabilities.map(c => c.id)), '$.exclusiveGroups');
271
+ }
272
+ else {
273
+ unique(a.bindings.map(b => b.capability), '$.bindings.capability');
274
+ for (const b of a.bindings) {
275
+ const count = b.claims.length;
276
+ if (b.state === 'configured' ? count !== 1 : b.state === 'conflicting' ? count < 2 : count !== 0)
277
+ fail('$', 'state/claims mismatch');
278
+ references(b.claims.map(c => c.target), nodes, '$.bindings.targets');
279
+ }
280
+ }
281
+ }
282
+ function verifyDescriptionContext(a, context) {
283
+ const research = a.records.map(ref => {
284
+ const r = context.find(r => r.id === ref.id && r.revision.id === ref.revision);
285
+ if (!r || r.contract !== 'n8r.research/0.1' || digest(r) !== ref.digest)
286
+ fail('$', 'research revision/digest unavailable');
287
+ return r;
288
+ });
289
+ const items = (bases, decisions = false) => bases.flatMap(b => {
290
+ const r = research.find(r => r.id === b.record && r.revision.id === b.revision);
291
+ return b.items.map(id => {
292
+ const item = (decisions ? r.decisions : r.findings).find(f => f.id === id);
293
+ if (!item)
294
+ fail('$', 'wrong or missing evidence item kind');
295
+ return item;
296
+ });
297
+ });
298
+ const findings = (bases) => items(bases);
299
+ if (a.contract === 'n8r.surface/0.3')
300
+ verifyRelationshipDetails(a, research);
301
+ const verifySupport = (c) => {
302
+ const fs = findings(c.basis);
303
+ if (fs.some(f => f.subject !== c.from || f.property !== 'supports'))
304
+ fail('$', 'capability evidence subject/property mismatch');
305
+ if (c.state === 'possible' && (!fs.length || fs.some(f => f.state !== 'known' || f.claims.some(cl => cl.value !== c.to))))
306
+ fail('$', 'possible requires supported target evidence');
307
+ if (c.state === 'absent' && (!fs.length || fs.some(f => f.state !== 'absent' || f.claims.some(cl => cl.value !== c.to))))
308
+ fail('$', 'absent requires scoped absence evidence');
309
+ if (c.state === 'conflicting' && !fs.some(f => f.state === 'conflicting'))
310
+ fail('$', 'conflicting requires conflicting finding');
311
+ };
312
+ if (a.kind === 'capability') {
313
+ for (const c of capabilityEdges(a))
314
+ verifySupport(c);
315
+ return;
316
+ }
317
+ const ref = a.capability;
318
+ const c = context.find(c => c.id === ref.id && c.revision.id === ref.revision);
319
+ if (!c || (c.contract !== 'n8r.surface/0.2' && c.contract !== 'n8r.surface/0.3') || c.contract !== a.contract || c.kind !== 'capability' || digest(c) !== ref.digest)
320
+ fail('$', 'capability revision/digest unavailable');
321
+ verifyDescriptionContext(c, context);
322
+ // A shared node ID denotes the same component; an instance is a distinct identity.
323
+ if ([c.id, ...c.nodes.map(n => n.id), ...capabilityEdges(c).map(e => e.id)].includes(a.instance.id))
324
+ fail('$', 'instance identity reused');
325
+ for (const n of a.nodes) {
326
+ const old = c.nodes.find(x => x.id === n.id);
327
+ if (old && canonical(old.type) !== canonical(n.type))
328
+ fail('$', 'shared component type changed');
329
+ }
330
+ if (a.contract === 'n8r.surface/0.3') {
331
+ for (const edge of a.relationships) {
332
+ const base = capabilityEdges(c).find(x => x.id === edge.id);
333
+ if (!base || edge.from !== base.from || edge.to !== base.to || canonical(edge.type) !== canonical(base.type))
334
+ fail('$', 'relationship differs from pinned Capability');
335
+ verifySupport({ ...edge, state: base.state });
336
+ }
337
+ }
338
+ for (const b of boundBindings(a)) {
339
+ const capability = capabilityEdges(c).find(c => c.id === b.capability);
340
+ if (!capability)
341
+ fail('$', 'unknown capability');
342
+ if (b.state === 'configured' && capability.state !== 'possible')
343
+ fail('$', 'configured requires established possibility');
344
+ items(b.decisions, true);
345
+ const targets = new Set(b.claims.map(cl => cl.target));
346
+ if (b.claims.length) {
347
+ // Do not allow an author to hide contrary evidence by omitting a basis item.
348
+ // Only this Bound snapshot's records apply; the pinned Capability may use
349
+ // older research revisions which must not override the current binding.
350
+ const matching = research.flatMap(r => r.findings).filter(f => f.subject === a.instance.id && f.property === b.capability &&
351
+ ['known', 'conflicting'].includes(f.state));
352
+ for (const f of matching) {
353
+ if (f.claims.some(cl => typeof cl.value !== 'string' || !targets.has(cl.value)) ||
354
+ (b.state === 'configured' && f.state === 'conflicting'))
355
+ fail('$', 'omitted contradictory configuration evidence');
356
+ }
357
+ }
358
+ for (const claim of b.claims) {
359
+ const fs = findings(claim.basis);
360
+ for (const f of fs) {
361
+ if (f.subject !== a.instance.id || f.property !== b.capability ||
362
+ !['known', 'conflicting'].includes(f.state) ||
363
+ !f.claims.some(cl => cl.value === claim.target) ||
364
+ f.claims.some(cl => typeof cl.value !== 'string' || !targets.has(cl.value)))
365
+ fail('$', 'binding evidence mismatch or suppressed conflict');
366
+ if (b.state === 'configured' && f.state !== 'known')
367
+ fail('$', 'conflict cannot establish configured');
368
+ }
369
+ }
370
+ }
371
+ for (const group of c.exclusiveGroups) {
372
+ if (boundBindings(a).filter(b => b.state === 'configured' && group.members.includes(b.capability)).length > 1)
373
+ fail('$', 'exclusive capabilities configured together');
374
+ }
375
+ }
376
+ function capabilityEdges(a) {
377
+ return a.contract === 'n8r.surface/0.3' ? a.relationships : a.capabilities;
378
+ }
379
+ function boundBindings(a) {
380
+ return a.contract === 'n8r.surface/0.3' ? a.relationships.map(e => ({ id: e.id, capability: e.id, ...e.binding })) : a.bindings;
381
+ }
382
+ const configLocation = (v, p) => {
383
+ text(v, p);
384
+ const s = v;
385
+ if (s.startsWith('/') || /[\\:?#%]/.test(s) || s.split('/').some(part => !part || part === '.' || part === '..'))
386
+ fail(p, 'requires safe relative documentation location');
387
+ };
388
+ const configReference = shape({ owner: id, mechanism: oneOf('environment-variable', 'file-key', 'argument', 'admin-setting', 'code'), key: text, location: configLocation });
389
+ const detailShape = (check) => (v, p) => {
390
+ shape({ state: oneOf('known', 'unknown', 'omitted', 'redacted', 'absent', 'out-of-scope', 'conflicting'),
391
+ claims: array(shape({ value: check, basis: array(basis, 1) })), note: text })(v, p);
392
+ const d = v;
393
+ const count = d.claims.length;
394
+ if (d.state === 'known' || d.state === 'absent') {
395
+ if (count !== 1)
396
+ fail(p, 'known/absent requires one evidenced claim');
397
+ }
398
+ else if (d.state === 'conflicting') {
399
+ if (count < 2 || new Set(d.claims.map(c => canonical(c))).size !== count)
400
+ fail(p, 'conflicting requires distinct evidenced claims');
401
+ }
402
+ else if (count)
403
+ fail(p, 'non-value state cannot carry claims');
404
+ };
405
+ const sharedRelationship = { id, from: id, to: id, type: descriptionType,
406
+ basis: array(basis), conditions: array(text), note: text,
407
+ purpose: detailShape(text), integration: detailShape(text), configuration: detailShape(array(configReference)) };
408
+ const bindingAnnotation = shape({
409
+ state: oneOf('configured', 'unknown', 'omitted', 'redacted', 'out-of-scope', 'conflicting'),
410
+ claims: array(shape({ target: id, basis: array(basis, 1) })), decisions: array(basis), note: text
411
+ });
412
+ const graphCommon = { ...descriptionCommon, contract: oneOf('n8r.surface/0.3') };
413
+ const capabilityGraphShape = shape({ ...graphCommon, kind: oneOf('capability'),
414
+ relationships: array(shape({ ...sharedRelationship, state: oneOf('possible', 'unknown', 'omitted', 'redacted', 'absent', 'out-of-scope', 'conflicting') })),
415
+ exclusiveGroups: array(shape({ id, members: array(id, 2) })) });
416
+ const boundGraphShape = shape({ ...graphCommon, kind: oneOf('bound'), capability: ref, instance: shape({ id, label: text }),
417
+ relationships: array(shape({ ...sharedRelationship, binding: bindingAnnotation })) });
418
+ const detailNames = ['purpose', 'integration', 'configuration'];
419
+ function validateGraph(input) {
420
+ const data = object(input, '$');
421
+ (data.kind === 'capability' ? capabilityGraphShape : boundGraphShape)(input, '$');
422
+ const a = input;
423
+ // Reuse the bounded 0.2 local state/identity checks without changing the input
424
+ // or the meaning/version of either serialized contract.
425
+ const legacy = a.kind === 'capability' ? {
426
+ ...a, contract: 'n8r.surface/0.2', capabilities: a.relationships.map(e => {
427
+ const { purpose, integration, configuration, ...core } = e;
428
+ return core;
429
+ })
430
+ } : { ...a, contract: 'n8r.surface/0.2', bindings: boundBindings(a) };
431
+ delete legacy.relationships;
432
+ validateDescription(legacy);
433
+ const nodes = new Set(a.nodes.map(n => n.id));
434
+ const vocab = new Set(a.vocabularies.map(v => v.id));
435
+ const records = new Set(a.records.map(r => JSON.stringify([r.id, r.revision])));
436
+ for (const e of a.relationships) {
437
+ references([e.from], nodes, '$.relationships.from');
438
+ references([e.to], nodes, '$.relationships.to');
439
+ references([e.type.vocabulary], vocab, '$.relationships.type');
440
+ const bases = [...e.basis, ...detailNames.flatMap(name => e[name].claims.flatMap(c => c.basis))];
441
+ for (const b of bases) {
442
+ references([JSON.stringify([b.record, b.revision])], records, '$.relationships.basis');
443
+ unique(b.items, '$.relationships.items');
444
+ }
445
+ for (const claim of e.configuration.claims)
446
+ for (const config of claim.value)
447
+ references([config.owner], nodes, '$.configuration.owner');
448
+ }
449
+ }
450
+ function verifyRelationshipDetails(a, research) {
451
+ for (const e of a.relationships)
452
+ for (const property of detailNames) {
453
+ const detail = e[property];
454
+ const values = new Set(detail.claims.map(c => canonical(c.value)));
455
+ const assertions = new Set();
456
+ for (const claim of detail.claims)
457
+ for (const b of claim.basis) {
458
+ const r = research.find(r => r.id === b.record && r.revision.id === b.revision);
459
+ for (const id of b.items) {
460
+ const f = r.findings.find(f => f.id === id);
461
+ if (!f || f.subject !== e.id || f.property !== property ||
462
+ (detail.state === 'known' ? f.state !== 'known' : detail.state === 'absent' ? f.state !== 'absent' : !['known', 'absent', 'conflicting'].includes(f.state)) ||
463
+ !f.claims.some(c => canonical(c.value) === canonical(claim.value)) ||
464
+ f.claims.some(c => !values.has(canonical(c.value))))
465
+ fail('$', 'relationship detail evidence mismatch');
466
+ assertions.add(f.state);
467
+ }
468
+ }
469
+ if (detail.state === 'conflicting' && values.size < 2 &&
470
+ !(assertions.has('known') && assertions.has('absent')))
471
+ fail('$', 'agreeing evidence does not establish conflict');
472
+ if (detail.claims.length) {
473
+ const matching = research.flatMap(r => r.findings).filter(f => f.subject === e.id && f.property === property);
474
+ for (const f of matching) {
475
+ if (f.claims.some(c => !values.has(canonical(c.value))) ||
476
+ (detail.state === 'known' && ['absent', 'conflicting'].includes(f.state)) ||
477
+ (detail.state === 'absent' && ['known', 'conflicting'].includes(f.state)))
478
+ fail('$', 'omitted contradictory relationship detail');
479
+ }
480
+ }
481
+ }
482
+ }
@@ -0,0 +1,72 @@
1
+ # CLI Capability and Bound bundles
2
+
3
+ The CLI candidate uses the same checked artifact machinery as Researcher skill
4
+ 0.1.11. New research can stage `n8r.surface/0.3` Capability or Bound bundles.
5
+ `/save surface` saves the preview's brief, research HTML, Surface JSON, pinned
6
+ dependencies and manifest in the next numbered directory. Reopening either
7
+ `research.html` or `surface.json` reads and checks the complete bundle.
8
+
9
+ Existing `n8r.surface/0.1` pairs remain readable and can be revised in their
10
+ original format. There is no automatic migration. A Capability and a Bound
11
+ Surface are separate identities; continue a saved description in its original
12
+ kind. Start a new discovery for a different kind. The CLI can also reopen a
13
+ standalone skill bundle. Such a bundle has no CLI runner receipt; historical
14
+ source references are rechecked rather than assigned invented runner provenance.
15
+
16
+ The runner receives bundled schema examples as synthetic shapes, not findings.
17
+ It computes canonical artifact hashes through a read-only host tool, then stages
18
+ a complete bundle. The host checks current and dependency research sources
19
+ against actual reads or saved evidence. Validation does not establish research
20
+ accuracy, complete secret detection, runtime use or consumer compatibility.
21
+ Use permitted sources or sanitized configuration summaries; do not supply secrets.
22
+
23
+ ## Human verification
24
+
25
+ Maintainer preparation: install the candidate in the tester's active Node
26
+ installation, confirm Codex sign-in/model access and vault setup, and open the
27
+ terminal in the existing prepared Lumen source directory. Do not ask the tester
28
+ to build packages or find paths. The checks below send that source to OpenAI
29
+ after confirmation. See [using the command](getting-started.md).
30
+
31
+ - [ ] Run `n8r-surface`. At `You>`, enter: “Research the AI connections in this
32
+ guide. Keep unknown configuration and runtime use explicit. My decision is to
33
+ defer runtime verification.” Confirm research. Expect findings based on the
34
+ guide and the recorded decision. Ask a follow-up question in your own words;
35
+ the conversation should let you explore freely.
36
+ - [ ] Type `save`. Open the brief or HTML at the returned path. Expect a useful
37
+ explanation of the connections, source evidence, unknowns and your decision.
38
+ Type `exit` when you want to leave. No separate Surface-preparation command is
39
+ required; saving does not end the conversation.
40
+ - [ ] Run `n8r-surface` again and select the saved title by its number. Expect
41
+ the previous research and decision to return without copying a file path.
42
+ Say: “Keep my runtime-verification deferral. Add my decision to defer external
43
+ OIDC investigation because model-service configuration is the priority.”
44
+ Then type `save`. Expect a new saved result with both decisions; the previous
45
+ result remains available and unchanged. Type `exit` to leave (exit 0).
46
+ - [ ] Report **Pass / Fail / Blocked** and any point where the experience was
47
+ confusing or the result was wrong, with the saved links. Wording and filenames
48
+ can vary. Missing saves, lost decisions or overwritten files fail. Missing
49
+ installation or sign-in is blocked. Keep the saved work; no cleanup is needed.
50
+
51
+ Maintainer evidence checks cover exact parent digests, preserved source/settings
52
+ bytes, dependency validation and the local recent-work index. They do not replace
53
+ the user's judgment of the conversation and result.
54
+
55
+ ## Maintainer evidence
56
+
57
+ At baseline `5eed2ac`, the installed CLI completed the first interaction but
58
+ saved `n8r.surface/0.1`. Candidate regression checks cover shared-helper readback,
59
+ fresh-store successors, dependency corruption, lost decisions, standalone skill
60
+ reopening and legacy pairs. Real-run evidence is recorded with the implementing
61
+ PR; automated checks are not human acceptance or publication evidence.
62
+
63
+ On 8 November 2026 an installed development tarball based on `5eed2ac` (dirty)
64
+ completed the exact two-session task above with Node 26.13.0, Codex 0.166.0 and
65
+ `gpt-6-astra` using ChatGPT sign-in. Both processes exited 0. Independent helper
66
+ readback verified Capability kind, exact research/Surface parents, retained
67
+ component IDs and runtime deferral, added OIDC deferral and pinned dependencies.
68
+ Source, settings and every predecessor file remained unchanged. Tarball SHA-256:
69
+ `0a4cc51d39327dfab67e186516f9eef1a01efcb1369c7ae35a9cb843e62c458f`.
70
+ This is an agent rehearsal, not a new human acceptance or registry release.
71
+ Subsequent changes broadened dependency-source checks and preserved optional
72
+ runner/note receipts; final candidate verification must identify its own bytes.
@@ -24,6 +24,54 @@ user: define a real task, attempt it through the product, observe the gap, devel
24
24
  what is missing, then repeat the same task. It does not publish private Lab
25
25
  implementation material or require that application to be running.
26
26
 
27
+ Candidate skill 0.1.10 adds standalone Capability/Bound artifact production and
28
+ revision, without adding a CLI command. See the [task, evidence and human
29
+ check](skill-surface-artifacts.md). Installed rehearsal and human acceptance remain
30
+ distinct from contract checks.
31
+
32
+ ## Open, research, save and return (#17)
33
+
34
+ Given a configured vault and signed-in Codex, running `n8r-surface` in a terminal
35
+ opens a research prompt and lists saved work by title. Type the investigation;
36
+ the current folder is the default source. Optional `/source PATH` changes it. Confirm source
37
+ transmission once, then converse normally. Type `save` to save the result and
38
+ `exit` to leave. Running `n8r-surface` again offers the saved title; selecting its
39
+ number reopens it with the remembered source and model. Save a successor without
40
+ replacing earlier files. Missing/moved sources require a new source selection.
41
+
42
+ Plain `save`, `save the result` and guided `/save` prepare a checked Surface if
43
+ needed and persist it only after validation. `/save note` saves Markdown.
44
+ Explicit `discover`, `reopen`, their options and legacy `/save` note behavior
45
+ remain available. Noninteractive startup retains the read-only overview.
46
+ A local recent-work index beside settings remembers source/model/output paths;
47
+ it is not part of portable Surface data. Index failure must not conceal an
48
+ otherwise successful artifact save. No saved outputs are removed by this flow.
49
+
50
+ Installed .3 baseline prints configuration and exits after vault readiness;
51
+ it offers no research prompt or remembered-work selection. Verification will
52
+ exercise bare startup, plain-language research/save, exit and fresh startup
53
+ selection through the installed command, then independently check the files.
54
+
55
+ ## CLI Capability/Bound increment (#17)
56
+
57
+ Given an installed candidate, a configured vault, Codex 0.166.0 signed in with
58
+ ChatGPT and permitted local sources, ask `discover SOURCE` to prepare a Capability
59
+ or Bound Surface. Enter `/save surface`, exit, then run
60
+ `reopen SAVED/surface.json --source SOURCE` and request a successor. The same
61
+ commands remain public; there is no new subcommand or automatic migration.
62
+
63
+ Expected: a preview distinguishes possibilities from configured bindings;
64
+ explicit save creates brief, HTML, JSON, dependencies and a checked manifest in
65
+ the next numbered directory. Fresh reopening retains exact dependencies,
66
+ identities, human decisions and parent references. Read the files independently
67
+ and compare predecessor/source bytes. Existing 0.1 pairs remain readable and
68
+ continue in their original format. No runtime-use or Labs compatibility follows.
69
+
70
+ The installed baseline at `5eed2ac` produced an older 0.1 definition for the
71
+ Capability request. The installed development candidate completed save/reopen/revise with independent
72
+ readback; see [the task and bounded evidence](cli-bundles.md). Final candidate
73
+ bytes and human acceptance remain separate from that rehearsal.
74
+
27
75
  ## Grow the specification with working verification
28
76
 
29
77
  Work one useful behavior at a time, including behavior that already exists. Add a
@@ -55,10 +103,12 @@ Current progress, with evidence defining the verified scope and revision:
55
103
 
56
104
  | User or consumer intent | Current progress | Verification / next increment |
57
105
  | --- | --- | --- |
106
+ | Create, repeat and reopen a standalone workspace through the skill. | Specified in accepted #41; helper and skill candidate 0.1.9 implemented. Installed-product scenarios and readback passed; overall PM acceptance pending. No new CLI command. | [Workspace contract](workspace.md), [human tasks](workspace-human-check.md), [evidence](workspace-verification.md). |
58
107
  | Create a security-focused skeleton and reconcile a later declaration change. | Skeleton Surface v0.1 guidance in installed skill 0.1.8; bounded agent brief/successor rehearsal and readback passed. Human acceptance pending; no new CLI command or Lab automation. | [SKELETON-01/02 prompts and readback](researcher-skeleton-check.md); [versioned results and remaining acceptance](researcher-skill-verification.md#skeleton-surface-018). |
59
108
  | Read saved research and selected definitions through the library, retaining identities, findings and references. | Implemented and verified for the experimental library profile; no command claim. | [CONTRACT-01 and repeatable fixture/package checks](verification.md#contract-01-retain-useful-attributable-research-and-selected-definitions). |
60
109
  | Run `n8r-surface` alone and see settings, saved artefacts and known process status. | Specified, implemented and verified for the read-only overview and guidance question; setup/conversation unavailable. | [STARTUP-01 installed evidence at `b90358a`](startup-verification.md). Historical read-only baseline; setup supersedes its guidance and exit behavior. |
61
110
  | Choose a local vault once and reuse it from another source directory. | Setup-only implementation; installed rehearsal and human acceptance tracked separately. | [SETUP-01 evidence and tasks](vault-setup-verification.md). Research and continuation remain pending. |
111
+ | Begin with a description and establish source access progressively. | Candidate 0.1.16 guidance; baseline rehearsal recorded, web acceptance tracked separately. No new CLI command or access adapter. | [First-use tasks and evidence](first-use-access.md). |
62
112
  | Research supplied sources through the installed skill and save working notes. | Implemented; bounded Lumen and LibreChat agent rehearsals have saved/read back notes. Actual human input survived fresh-agent continuation; Work UI acceptance remains pending. | [RESEARCH-01 evidence and limits](researcher-skill-verification.md). No structured-output or standalone-runner claim. |
63
113
  | Offer to retain an initial partial graph when saving is undecided. | Implemented in skill 0.1.7; acceptance and evidence are tracked separately. | [Optional save offer](researcher-skill-verification.md#optional-save-offer-017). |
64
114
  | Resume from saved recommendations and retain useful next investigations. | Implemented in skill 0.1.6; installed fresh-agent save/continue and independent readback passed. Human acceptance remains pending. | [Recommendations evidence](researcher-skill-verification.md#saved-recommendations-016) and [prepared interaction](researcher-prepared-check.md). |
@@ -545,8 +595,8 @@ validation and read-only release mapping verification. Synthetic Git repositorie
545
595
  exercise missing/wrong tags, dirty source, altered archives and release metadata;
546
596
  they do not establish an authenticated release or registry provenance. The public
547
597
  CLI inventory is unchanged; `check:version` and `check:release` are maintainer npm
548
- scripts. Real release verification remains blocked until an authorized release
549
- and publishing setup exist.
598
+ scripts. The subsequent [scoped release](npm-release.md#verified-scoped-release)
599
+ adds authenticated release and registry evidence beyond those synthetic checks.
550
600
 
551
601
  ## RELEASE-01: prepare and publish the first dual release
552
602
 
@@ -560,5 +610,102 @@ The manual workflow prepares retained archives before publication and verifies
560
610
  existing registry bytes before staging only absent versions for maintainer 2FA approval. Tests cover altered
561
611
  bytes, wrong registry identity/integrity, partial-publication recovery and network
562
612
  failure. These use synthetic registry boundaries, not authenticated publication.
563
- Live workflow, registry installation, cryptographic provenance and immutable
564
- release acceptance remain unverified until the documented release procedure runs.
613
+ Before the release procedure ran, live workflow, registry installation,
614
+ cryptographic provenance and immutable release acceptance were unverified.
615
+ The subsequent scoped-only result is recorded below.
616
+
617
+ ### Verified first-release outcome
618
+
619
+ The maintainer narrowed RELEASE-01 to scoped-only publication. On 5 November
620
+ 2026, `@n8r/surface@0.1.0-experimental.2` passed exact registry byte/integrity,
621
+ installed library/assets/CLI, isolated startup and signed-provenance checks.
622
+ The GitHub prerelease was finalized and read back as immutable. See the
623
+ [release evidence](npm-release.md#verified-scoped-release). The unscoped
624
+ submission failed with E403 and was removed from the release scope; no unscoped
625
+ registry success or name protection is claimed. Future workflow adaptation
626
+ must remove dual-package assumptions before another scoped release. Unscoped
627
+ publication and name reservation are no longer planned.
628
+
629
+ ## SKILL-PRESENTATION-01: read a useful checked report
630
+
631
+ **Implemented candidate 0.1.11.** Given a saved checked Capability/Bound bundle,
632
+ ask the installed Researcher to save a readable presentation copy in a new folder,
633
+ using the same bundle and original predecessor. Expect the current conclusion,
634
+ what changed, bounded significance, cited evidence, decisions and unknowns in
635
+ both HTML and brief. Exact-parent comparison is unavailable when parent research
636
+ is not retained. All checked data and original bytes remain unchanged; the new
637
+ manifest covers the new presentation. No new research or CLI behavior is added.
638
+ See the [human task and readback](skill-surface-artifacts.md#short-human-check).
639
+
640
+ ## SKILL-SURFACE-02: save and continue an investigation
641
+
642
+ **Candidate 0.1.13 implemented; installed assessment tracked separately.**
643
+ No new product CLI command. Given current-chat supplied sources and an authorized
644
+ Surface destination, ask: “Explain the AI paths, save the Surface and maintain an
645
+ investigation journal as we go.” Expect an initial plan, possible/selected paths,
646
+ inspectable evidence, useful next options and numbered saved revisions.
647
+
648
+ The state change is a self-contained CycloneDX BOM plus derived brief, HTML map,
649
+ Markdown journal and manifest. Given only the latest BOM in a fresh chat, ask to
650
+ continue: evidence, decisions, uncertainties and investigation state must recover.
651
+ A user-directed diversion remains recorded, with deferred work and its reason.
652
+ Earlier bytes remain unchanged. View edits require explicit reconciliation.
653
+ See the [human tasks and evidence](skill-bom-verification.md) and
654
+ [working manual](skill-manual.md). Automated profile checks do not establish
655
+ research accuracy or human acceptance.
656
+
657
+ ## SKILL-OVERVIEW-01: understand a supplied application
658
+
659
+ Given the identified 0.1.14 candidate, readable source and a new output folder,
660
+ when the user asks for an initial architecture/security overview and saves it,
661
+ the brief explains application meaning, major areas, evidence-backed implications,
662
+ coverage gaps and next decisions. The HTML shows connected nodes once, before
663
+ relationship/evidence detail. Intended choices remain separate from configuration
664
+ and dated observations. Existing revisions stay unchanged.
665
+
666
+ Specified in [the first-use verification](first-use-overview.md); renderer and
667
+ research guidance implemented. Installed rehearsal and human acceptance status
668
+ are recorded there. Automated structure checks alone do not verify this outcome.
669
+
670
+ ### SKILL-OVERVIEW-02: continue a source-only investigation
671
+
672
+ Given a saved source investigation with historical runtime blockers and completed
673
+ work, say: “It is just a source checkout. Record that in the Surface, keep
674
+ deployment settings unverified, and show the most useful source-level investigation
675
+ next. Save a successor.” Expect a short current scope, reconciled findings/gaps,
676
+ one recommended source action, and unchanged predecessor bytes. Obsolete blockers
677
+ and actions remain history, not current requirements. No runtime inventory or
678
+ service startup is needed. The brief discloses omitted list items and links full
679
+ detail; it retains limitations beside displayed findings.
680
+
681
+ For separately requested inspection of an identified runtime target, emit only
682
+ that target's relevant safe fields. A broad response must be filtered inside the
683
+ collecting process before output; saved redaction alone is insufficient.
684
+ See [rehearsal evidence and human checks](first-use-overview.md#follow-up-rehearsal).
685
+
686
+ ## SKILL-INSTANCE-01: understand and revisit a running composition
687
+
688
+ Candidate 0.1.15 adds research guidance; no product CLI command or access adapter.
689
+ Given an identified target, permitted host access to sources/instance evidence and
690
+ a private destination, ask: “Understand this running system, show what is
691
+ configured, observed and untested, and save the Surface.” Expect source identity,
692
+ scoped observations, a useful composition beyond a single repository, explicit
693
+ access gaps and saved BOM/brief/map/journal readback. No probes or service changes.
694
+
695
+ Continue from that BOM with current permitted evidence. Expect unchanged stable
696
+ identities and predecessor bytes, with actual target changes distinguished from
697
+ newly learned facts, unchanged checked facts, lost coverage and conflicts. A host
698
+ without required access or saving tools must report the blocked portion without
699
+ fabricating results. Source-only requests need no runtime inventory.
700
+ See the [human tasks and bounded verification](instance-first-research.md).
701
+
702
+ ## SKILL-ACCESS-01: begin without prepared connections
703
+
704
+ Given a fresh session and a description/document, with laptop source and VPS
705
+ access only mentioned, ask Researcher to understand the system and help provide
706
+ sources. Expect a provisional map, one useful next source and host-appropriate
707
+ access guidance. A project upload is not a live folder; SSH details are not access.
708
+ Supply another source and expect progress rather than repeated intake. Saving
709
+ requires observed helper/runtime/delivery capabilities; otherwise retain copyable
710
+ findings and name the blocked step. No credentials in chat or target mutations.
711
+ See [exact interactions, readback and verification status](first-use-access.md).
@@ -12,6 +12,16 @@ make this profile concrete. Other languages can implement the same data contract
12
12
  The library reads supplied strings and returns values/strings; it does not read
13
13
  files, fetch references, call inference or write a corpus.
14
14
 
15
+ The separate JSON-only [Capability and Bound contract 0.2](surface-description-contract.md)
16
+ adds possible relationships and configured instance bindings without reinterpreting
17
+ these selected `0.1` definitions. The APIs accept both versions; their meanings
18
+ and fields remain distinct. Installed-skill production is separate work.
19
+
20
+ The [shared relationship contract 0.3](surface-relationships.md) adds the same
21
+ component-node and relationship-edge structure to both descriptions, including
22
+ evidenced purpose, integration mechanism and safe configuration references.
23
+ Existing `0.1` and `0.2` representations remain readable.
24
+
15
25
  ## Common envelope and identity
16
26
 
17
27
  Every artefact has these required fields. Objects are closed except the explicit
@@ -1,5 +1,26 @@
1
1
  # n8r-surface critical path
2
2
 
3
+ The [security assurance strategy agreed on 12 November 2026](security-assurance-strategy.md)
4
+ sets the destination: a consultant-led framework connecting policy and levels to
5
+ controls, implementation and observable tests. Its
6
+ [milestone map](security-assurance-strategy.md#existing-work-and-proposed-next-milestones)
7
+ places the existing skill, pending acceptance and proposed first framework slice
8
+ in that context. The delivery history below does not define a complete assurance
9
+ framework or establish a posture score.
10
+
11
+ The [product direction captured on 11 November 2026](product-direction.md)
12
+ proposes a first value demonstration: installed-skill discovery across sources,
13
+ a bounded security finding on an integrated test system, and a verified defence.
14
+ It accepts unintegrated sources for discovery and requires runtime evidence for
15
+ runtime claims. This is a proposed acceptance target, not implementation or
16
+ rehearsal evidence; the delivery history and existing obligations below remain.
17
+
18
+ The next installed-skill delivery agreed on 8 November 2026 is
19
+ [Capability and Bound Surfaces from workspace artifacts](surface-descriptions.md),
20
+ tracked by #50 → #51 → #52 under #17. Observed Surface is deferred. The earlier
21
+ CLI sequence and evidence below remain historical context, not this increment's
22
+ completion criteria.
23
+
3
24
  The user updated delivery order on 5 November 2026: standalone Codex research,
4
25
  Markdown save/reopen, then partial Surface generation and a complete fresh-session
5
26
  revision proof. npm distribution ([#27](https://github.com/injectionator/n8r-surface/issues/27))