wingfoil 0.2.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/LICENSE +21 -0
- package/README.md +334 -0
- package/dist/cli/error.d.ts +11 -0
- package/dist/cli/error.d.ts.map +1 -0
- package/dist/cli/error.js +27 -0
- package/dist/cli/error.js.map +1 -0
- package/dist/cli/exit.d.ts +19 -0
- package/dist/cli/exit.d.ts.map +1 -0
- package/dist/cli/exit.js +14 -0
- package/dist/cli/exit.js.map +1 -0
- package/dist/cli/index.d.ts +28 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +36 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-command.d.ts +34 -0
- package/dist/cli/init-command.d.ts.map +1 -0
- package/dist/cli/init-command.js +100 -0
- package/dist/cli/init-command.js.map +1 -0
- package/dist/cli/mcp-command.d.ts +24 -0
- package/dist/cli/mcp-command.d.ts.map +1 -0
- package/dist/cli/mcp-command.js +42 -0
- package/dist/cli/mcp-command.js.map +1 -0
- package/dist/cli/output.d.ts +13 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +28 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/program.d.ts +12 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +279 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/registrar.d.ts +69 -0
- package/dist/cli/registrar.d.ts.map +1 -0
- package/dist/cli/registrar.js +82 -0
- package/dist/cli/registrar.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +45 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/approval-authority.d.ts +83 -0
- package/dist/core/approval-authority.d.ts.map +1 -0
- package/dist/core/approval-authority.js +112 -0
- package/dist/core/approval-authority.js.map +1 -0
- package/dist/core/builtin-asset.d.ts +71 -0
- package/dist/core/builtin-asset.d.ts.map +1 -0
- package/dist/core/builtin-asset.js +137 -0
- package/dist/core/builtin-asset.js.map +1 -0
- package/dist/core/builtin-integrity.d.ts +32 -0
- package/dist/core/builtin-integrity.d.ts.map +1 -0
- package/dist/core/builtin-integrity.js +148 -0
- package/dist/core/builtin-integrity.js.map +1 -0
- package/dist/core/confinement.d.ts +57 -0
- package/dist/core/confinement.d.ts.map +1 -0
- package/dist/core/confinement.js +108 -0
- package/dist/core/confinement.js.map +1 -0
- package/dist/core/context.d.ts +187 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +192 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/directive-assign.d.ts +131 -0
- package/dist/core/directive-assign.d.ts.map +1 -0
- package/dist/core/directive-assign.js +352 -0
- package/dist/core/directive-assign.js.map +1 -0
- package/dist/core/directives-list.d.ts +80 -0
- package/dist/core/directives-list.d.ts.map +1 -0
- package/dist/core/directives-list.js +148 -0
- package/dist/core/directives-list.js.map +1 -0
- package/dist/core/exit-code.d.ts +85 -0
- package/dist/core/exit-code.d.ts.map +1 -0
- package/dist/core/exit-code.js +163 -0
- package/dist/core/exit-code.js.map +1 -0
- package/dist/core/git-identity.d.ts +32 -0
- package/dist/core/git-identity.d.ts.map +1 -0
- package/dist/core/git-identity.js +72 -0
- package/dist/core/git-identity.js.map +1 -0
- package/dist/core/index.d.ts +388 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +1331 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/init.d.ts +78 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +167 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/loaders.d.ts +174 -0
- package/dist/core/loaders.d.ts.map +1 -0
- package/dist/core/loaders.js +364 -0
- package/dist/core/loaders.js.map +1 -0
- package/dist/core/memory-add-type.d.ts +43 -0
- package/dist/core/memory-add-type.d.ts.map +1 -0
- package/dist/core/memory-add-type.js +151 -0
- package/dist/core/memory-add-type.js.map +1 -0
- package/dist/core/memory-transition.d.ts +111 -0
- package/dist/core/memory-transition.d.ts.map +1 -0
- package/dist/core/memory-transition.js +269 -0
- package/dist/core/memory-transition.js.map +1 -0
- package/dist/core/registry.d.ts +189 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +66 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/relevance.d.ts +86 -0
- package/dist/core/relevance.d.ts.map +1 -0
- package/dist/core/relevance.js +258 -0
- package/dist/core/relevance.js.map +1 -0
- package/dist/core/require-reason.d.ts +27 -0
- package/dist/core/require-reason.d.ts.map +1 -0
- package/dist/core/require-reason.js +95 -0
- package/dist/core/require-reason.js.map +1 -0
- package/dist/core/types.d.ts +45 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +22 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/usage-error.d.ts +20 -0
- package/dist/core/usage-error.d.ts.map +1 -0
- package/dist/core/usage-error.js +29 -0
- package/dist/core/usage-error.js.map +1 -0
- package/dist/core/write-guard.d.ts +99 -0
- package/dist/core/write-guard.d.ts.map +1 -0
- package/dist/core/write-guard.js +247 -0
- package/dist/core/write-guard.js.map +1 -0
- package/dist/directives/create.d.ts +47 -0
- package/dist/directives/create.d.ts.map +1 -0
- package/dist/directives/create.js +82 -0
- package/dist/directives/create.js.map +1 -0
- package/dist/directives/index.d.ts +20 -0
- package/dist/directives/index.d.ts.map +1 -0
- package/dist/directives/index.js +32 -0
- package/dist/directives/index.js.map +1 -0
- package/dist/directives/roles-edit.d.ts +53 -0
- package/dist/directives/roles-edit.d.ts.map +1 -0
- package/dist/directives/roles-edit.js +286 -0
- package/dist/directives/roles-edit.js.map +1 -0
- package/dist/directives/schema.d.ts +65 -0
- package/dist/directives/schema.d.ts.map +1 -0
- package/dist/directives/schema.js +68 -0
- package/dist/directives/schema.js.map +1 -0
- package/dist/dna/edit.d.ts +61 -0
- package/dist/dna/edit.d.ts.map +1 -0
- package/dist/dna/edit.js +429 -0
- package/dist/dna/edit.js.map +1 -0
- package/dist/dna/index.d.ts +24 -0
- package/dist/dna/index.d.ts.map +1 -0
- package/dist/dna/index.js +59 -0
- package/dist/dna/index.js.map +1 -0
- package/dist/dna/mutate.d.ts +74 -0
- package/dist/dna/mutate.d.ts.map +1 -0
- package/dist/dna/mutate.js +318 -0
- package/dist/dna/mutate.js.map +1 -0
- package/dist/dna/path.d.ts +175 -0
- package/dist/dna/path.d.ts.map +1 -0
- package/dist/dna/path.js +289 -0
- package/dist/dna/path.js.map +1 -0
- package/dist/dna/roles.d.ts +57 -0
- package/dist/dna/roles.d.ts.map +1 -0
- package/dist/dna/roles.js +50 -0
- package/dist/dna/roles.js.map +1 -0
- package/dist/dna/schema.d.ts +172 -0
- package/dist/dna/schema.d.ts.map +1 -0
- package/dist/dna/schema.js +182 -0
- package/dist/dna/schema.js.map +1 -0
- package/dist/dna/set.d.ts +145 -0
- package/dist/dna/set.d.ts.map +1 -0
- package/dist/dna/set.js +428 -0
- package/dist/dna/set.js.map +1 -0
- package/dist/mcp/dna-resource.d.ts +16 -0
- package/dist/mcp/dna-resource.d.ts.map +1 -0
- package/dist/mcp/dna-resource.js +56 -0
- package/dist/mcp/dna-resource.js.map +1 -0
- package/dist/mcp/index.d.ts +49 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +60 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/memory-resource.d.ts +17 -0
- package/dist/mcp/memory-resource.d.ts.map +1 -0
- package/dist/mcp/memory-resource.js +133 -0
- package/dist/mcp/memory-resource.js.map +1 -0
- package/dist/mcp/prompt.d.ts +48 -0
- package/dist/mcp/prompt.d.ts.map +1 -0
- package/dist/mcp/prompt.js +176 -0
- package/dist/mcp/prompt.js.map +1 -0
- package/dist/mcp/read-only.d.ts +42 -0
- package/dist/mcp/read-only.d.ts.map +1 -0
- package/dist/mcp/read-only.js +93 -0
- package/dist/mcp/read-only.js.map +1 -0
- package/dist/mcp/registrar.d.ts +54 -0
- package/dist/mcp/registrar.d.ts.map +1 -0
- package/dist/mcp/registrar.js +72 -0
- package/dist/mcp/registrar.js.map +1 -0
- package/dist/mcp/server.d.ts +58 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +64 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/workflow-resource.d.ts +17 -0
- package/dist/mcp/workflow-resource.d.ts.map +1 -0
- package/dist/mcp/workflow-resource.js +63 -0
- package/dist/mcp/workflow-resource.js.map +1 -0
- package/dist/memory/add.d.ts +50 -0
- package/dist/memory/add.d.ts.map +1 -0
- package/dist/memory/add.js +131 -0
- package/dist/memory/add.js.map +1 -0
- package/dist/memory/audit.d.ts +177 -0
- package/dist/memory/audit.d.ts.map +1 -0
- package/dist/memory/audit.js +287 -0
- package/dist/memory/audit.js.map +1 -0
- package/dist/memory/commit-message.d.ts +146 -0
- package/dist/memory/commit-message.d.ts.map +1 -0
- package/dist/memory/commit-message.js +203 -0
- package/dist/memory/commit-message.js.map +1 -0
- package/dist/memory/entry.d.ts +56 -0
- package/dist/memory/entry.d.ts.map +1 -0
- package/dist/memory/entry.js +28 -0
- package/dist/memory/entry.js.map +1 -0
- package/dist/memory/frontmatter-edit.d.ts +68 -0
- package/dist/memory/frontmatter-edit.d.ts.map +1 -0
- package/dist/memory/frontmatter-edit.js +333 -0
- package/dist/memory/frontmatter-edit.js.map +1 -0
- package/dist/memory/git-log.d.ts +32 -0
- package/dist/memory/git-log.d.ts.map +1 -0
- package/dist/memory/git-log.js +100 -0
- package/dist/memory/git-log.js.map +1 -0
- package/dist/memory/history.d.ts +142 -0
- package/dist/memory/history.d.ts.map +1 -0
- package/dist/memory/history.js +250 -0
- package/dist/memory/history.js.map +1 -0
- package/dist/memory/index.d.ts +58 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +107 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/query.d.ts +150 -0
- package/dist/memory/query.d.ts.map +1 -0
- package/dist/memory/query.js +307 -0
- package/dist/memory/query.js.map +1 -0
- package/dist/memory/reject.d.ts +11 -0
- package/dist/memory/reject.d.ts.map +1 -0
- package/dist/memory/reject.js +32 -0
- package/dist/memory/reject.js.map +1 -0
- package/dist/memory/schema.d.ts +104 -0
- package/dist/memory/schema.d.ts.map +1 -0
- package/dist/memory/schema.js +161 -0
- package/dist/memory/schema.js.map +1 -0
- package/dist/memory/state-machine.d.ts +166 -0
- package/dist/memory/state-machine.d.ts.map +1 -0
- package/dist/memory/state-machine.js +431 -0
- package/dist/memory/state-machine.js.map +1 -0
- package/dist/memory/submit.d.ts +11 -0
- package/dist/memory/submit.d.ts.map +1 -0
- package/dist/memory/submit.js +44 -0
- package/dist/memory/submit.js.map +1 -0
- package/dist/storage/builtin-directives.d.ts +54 -0
- package/dist/storage/builtin-directives.d.ts.map +1 -0
- package/dist/storage/builtin-directives.js +152 -0
- package/dist/storage/builtin-directives.js.map +1 -0
- package/dist/storage/commit.d.ts +92 -0
- package/dist/storage/commit.d.ts.map +1 -0
- package/dist/storage/commit.js +201 -0
- package/dist/storage/commit.js.map +1 -0
- package/dist/storage/confinement.d.ts +93 -0
- package/dist/storage/confinement.d.ts.map +1 -0
- package/dist/storage/confinement.js +164 -0
- package/dist/storage/confinement.js.map +1 -0
- package/dist/storage/document.d.ts +27 -0
- package/dist/storage/document.d.ts.map +1 -0
- package/dist/storage/document.js +52 -0
- package/dist/storage/document.js.map +1 -0
- package/dist/storage/errors.d.ts +36 -0
- package/dist/storage/errors.d.ts.map +1 -0
- package/dist/storage/errors.js +46 -0
- package/dist/storage/errors.js.map +1 -0
- package/dist/storage/frontmatter.d.ts +27 -0
- package/dist/storage/frontmatter.d.ts.map +1 -0
- package/dist/storage/frontmatter.js +37 -0
- package/dist/storage/frontmatter.js.map +1 -0
- package/dist/storage/git-root.d.ts +17 -0
- package/dist/storage/git-root.d.ts.map +1 -0
- package/dist/storage/git-root.js +49 -0
- package/dist/storage/git-root.js.map +1 -0
- package/dist/storage/index.d.ts +29 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +66 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/init-state.d.ts +9 -0
- package/dist/storage/init-state.d.ts.map +1 -0
- package/dist/storage/init-state.js +24 -0
- package/dist/storage/init-state.js.map +1 -0
- package/dist/storage/layout.d.ts +51 -0
- package/dist/storage/layout.d.ts.map +1 -0
- package/dist/storage/layout.js +88 -0
- package/dist/storage/layout.js.map +1 -0
- package/dist/storage/memory-path.d.ts +72 -0
- package/dist/storage/memory-path.d.ts.map +1 -0
- package/dist/storage/memory-path.js +127 -0
- package/dist/storage/memory-path.js.map +1 -0
- package/dist/storage/snapshot.d.ts +16 -0
- package/dist/storage/snapshot.d.ts.map +1 -0
- package/dist/storage/snapshot.js +72 -0
- package/dist/storage/snapshot.js.map +1 -0
- package/dist/storage/templates.d.ts +88 -0
- package/dist/storage/templates.d.ts.map +1 -0
- package/dist/storage/templates.js +422 -0
- package/dist/storage/templates.js.map +1 -0
- package/dist/validation/error-mapper.d.ts +16 -0
- package/dist/validation/error-mapper.d.ts.map +1 -0
- package/dist/validation/error-mapper.js +18 -0
- package/dist/validation/error-mapper.js.map +1 -0
- package/dist/validation/errors.d.ts +69 -0
- package/dist/validation/errors.d.ts.map +1 -0
- package/dist/validation/errors.js +69 -0
- package/dist/validation/errors.js.map +1 -0
- package/dist/validation/id.d.ts +16 -0
- package/dist/validation/id.d.ts.map +1 -0
- package/dist/validation/id.js +177 -0
- package/dist/validation/id.js.map +1 -0
- package/dist/validation/index.d.ts +18 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +37 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/secret-scan.d.ts +129 -0
- package/dist/validation/secret-scan.d.ts.map +1 -0
- package/dist/validation/secret-scan.js +367 -0
- package/dist/validation/secret-scan.js.map +1 -0
- package/dist/validation/two-pass.d.ts +24 -0
- package/dist/validation/two-pass.d.ts.map +1 -0
- package/dist/validation/two-pass.js +24 -0
- package/dist/validation/two-pass.js.map +1 -0
- package/dist/validation/warning.d.ts +31 -0
- package/dist/validation/warning.d.ts.map +1 -0
- package/dist/validation/warning.js +81 -0
- package/dist/validation/warning.js.map +1 -0
- package/dist/validation/yaml.d.ts +9 -0
- package/dist/validation/yaml.d.ts.map +1 -0
- package/dist/validation/yaml.js +30 -0
- package/dist/validation/yaml.js.map +1 -0
- package/dist/workflow/index.d.ts +10 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +16 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/schema.d.ts +89 -0
- package/dist/workflow/schema.d.ts.map +1 -0
- package/dist/workflow/schema.js +73 -0
- package/dist/workflow/schema.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyBuiltinTemplates = verifyBuiltinTemplates;
|
|
4
|
+
/**
|
|
5
|
+
* Built-in template schema check (task-044-builtin-template-integrity, REQ-SEC-10 "Schema checks on
|
|
6
|
+
* built-in templates"): "Built-in directive and workflow templates are schema-checked before
|
|
7
|
+
* installation during `init`... A corrupted or schema-invalid built-in template aborts `init` before
|
|
8
|
+
* writing partial assets, with a message naming the failing template"
|
|
9
|
+
* (docs/02_requirements/03_sard/05_security-compliance.md).
|
|
10
|
+
*
|
|
11
|
+
* SCOPE, per `dl-031-req-sec-10-integrity-depth` (`ready`), which retitled the requirement: schema
|
|
12
|
+
* validation IS the REQ-SEC-10 contract. There is deliberately NO digest and NO manifest here — the
|
|
13
|
+
* threat the requirement's rationale names is accidental corruption (truncated file, bad merge), not
|
|
14
|
+
* post-install tampering; a manifest shipped inside the same package is no control against an
|
|
15
|
+
* adversary who can rewrite a template; and a content hash would add a line-ending / encoding /
|
|
16
|
+
* ordering determinism surface against REQ-SYS-07. Distribution-channel assurance lives in `adr-009`
|
|
17
|
+
* / `spec-015` (npm provenance) instead. Do not "strengthen" this module with hashing.
|
|
18
|
+
*
|
|
19
|
+
* A cross-pillar concern by construction — checking a directive source needs the Directives pillar's
|
|
20
|
+
* `DirectiveFrontmatter` schema, checking a workflow source needs the Workflow pillar's `Workflow`
|
|
21
|
+
* schema — so, per `src/core/loaders.ts`'s own precedent ("Cross-file concerns ... need the caller to
|
|
22
|
+
* have actually loaded the referenced files ... live in the loader, not [a pillar] schema module"),
|
|
23
|
+
* this lives in `core`, not in either pillar module or in `storage` (which only carries the raw
|
|
24
|
+
* {@link BuiltinTemplateSource} shape — see `src/storage/templates.ts`). Reuses the exact same
|
|
25
|
+
* two-pass validation primitives (`parseYaml`, `runValidation`) every pillar loader already uses, so a
|
|
26
|
+
* built-in source is checked against precisely the same schema its custom counterpart would be loaded
|
|
27
|
+
* with — no bespoke re-implementation of YAML/frontmatter parsing.
|
|
28
|
+
*
|
|
29
|
+
* `verifyBuiltinTemplates` is a pure function of its input list (REQ-SYS-07: no wall-clock, no
|
|
30
|
+
* randomness) — the same corrupted input always yields the same failure, and callers may pass a
|
|
31
|
+
* fixture list in tests via {@link BuiltinTemplateSource} without touching disk.
|
|
32
|
+
*/
|
|
33
|
+
const schema_1 = require("../directives/schema");
|
|
34
|
+
const storage_1 = require("../storage");
|
|
35
|
+
const validation_1 = require("../validation");
|
|
36
|
+
const schema_2 = require("../workflow/schema");
|
|
37
|
+
/**
|
|
38
|
+
* `true` iff `source.content` parses as a `.md` document with a frontmatter block that validates
|
|
39
|
+
* against `DirectiveFrontmatter` (the same schema `core/loaders.ts`'s `loadDirectives` runs custom
|
|
40
|
+
* directive files through). Any parse or schema failure — missing frontmatter, unparsable YAML, a
|
|
41
|
+
* missing required field — is "corrupted" for REQ-SEC-10's purposes; the exact cause is not surfaced
|
|
42
|
+
* (the fit criterion only requires naming *which* template failed, not why).
|
|
43
|
+
*/
|
|
44
|
+
function isValidDirectiveSource(source) {
|
|
45
|
+
const frontmatterText = (0, storage_1.extractFrontmatter)(source.content);
|
|
46
|
+
if (frontmatterText === null)
|
|
47
|
+
return false;
|
|
48
|
+
try {
|
|
49
|
+
const data = (0, validation_1.parseYaml)(frontmatterText, source.name);
|
|
50
|
+
(0, validation_1.runValidation)(schema_1.DirectiveFrontmatter, data, source.name);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* `true` iff `source.content` parses as YAML and validates against the `Workflow` schema (the same
|
|
59
|
+
* schema `core/loaders.ts`'s `loadWorkflowsYaml` runs every included workflow file through).
|
|
60
|
+
*/
|
|
61
|
+
function isValidWorkflowSource(source) {
|
|
62
|
+
try {
|
|
63
|
+
const data = (0, validation_1.parseYaml)(source.content, source.name);
|
|
64
|
+
(0, validation_1.runValidation)(schema_2.Workflow, data, source.name);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Per-kind schema-check policy: the `isValid` predicate to apply and the exact REQ-SEC-10 abort-message
|
|
73
|
+
* builder to use when it fails. Keyed by {@link BuiltinTemplateKind} so each kind's validator and its
|
|
74
|
+
* BDD wording live together and `verifyBuiltinTemplates` branches on `kind` exactly once. The message
|
|
75
|
+
* strings are verbatim BDD contracts — P3.8 "Error - a built-in template fails its integrity check"
|
|
76
|
+
* and P4.17 "Error - a built-in workflow template is structurally invalid" — do not reword.
|
|
77
|
+
*
|
|
78
|
+
* Declared as an EXHAUSTIVE `Record` so widening {@link BuiltinTemplateKind} is a compile error until
|
|
79
|
+
* the new kind gets a policy; look it up only through {@link policyFor}, never by bare indexing.
|
|
80
|
+
*/
|
|
81
|
+
const INTEGRITY_POLICY = {
|
|
82
|
+
directive: {
|
|
83
|
+
isValid: isValidDirectiveSource,
|
|
84
|
+
message: (name) => `built-in directive template integrity check failed: ${name}`,
|
|
85
|
+
},
|
|
86
|
+
workflow: {
|
|
87
|
+
isValid: isValidWorkflowSource,
|
|
88
|
+
message: (name) => `built-in workflow template invalid: ${name}`,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* The REQ-SEC-10 message for a source whose `kind` has no policy — the fail-closed branch. Not a BDD
|
|
93
|
+
* contract string (the P3.8/P4.17 scenarios only cover the two known kinds), but it obeys the fit
|
|
94
|
+
* criterion's one requirement of every abort message: it NAMES the failing template. The offending
|
|
95
|
+
* `kind` is echoed so the operator can see what arrived.
|
|
96
|
+
*/
|
|
97
|
+
function unknownKindMessage(name, kind) {
|
|
98
|
+
return `built-in template integrity check failed: ${name} (unrecognized kind "${kind}")`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The policy for `kind`, or `undefined` when there is none.
|
|
102
|
+
*
|
|
103
|
+
* `INTEGRITY_POLICY[kind]` on its own is FAIL-OPEN at runtime: TypeScript types the result as always
|
|
104
|
+
* present, but a `kind` outside the union — reachable through a cast, a JS caller, a JSON boundary, or
|
|
105
|
+
* a future disk-derived source list — yields `undefined` (or, for `constructor`/`toString`/`__proto__`,
|
|
106
|
+
* an inherited `Object.prototype` member), and the call on it throws a `TypeError`. That throw escapes
|
|
107
|
+
* `initWingfoilProject`, whose guard-5 call sits outside its `try`/`catch`, as an uncaught exception
|
|
108
|
+
* instead of the `VALIDATION` CoreResult / exit 1 REQ-SEC-10 demands. `hasOwnProperty` keeps inherited
|
|
109
|
+
* members out, so an unrecognized kind resolves to `undefined` and the caller fails it closed.
|
|
110
|
+
*/
|
|
111
|
+
function policyFor(kind) {
|
|
112
|
+
return Object.prototype.hasOwnProperty.call(INTEGRITY_POLICY, kind) ? INTEGRITY_POLICY[kind] : undefined;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Schema-check every `sources` entry, in list order (REQ-SYS-07: deterministic, no unordered
|
|
116
|
+
* iteration), and return the FIRST one that fails — or `null` when every source is valid (including
|
|
117
|
+
* the trivial, always-passing case of an empty list, which is what `src/storage/templates.ts`'s
|
|
118
|
+
* `builtinTemplateSources` derives from the P1.1 minimal skeleton, whose built-in directory holds only
|
|
119
|
+
* a `.gitkeep`; `templateScaffold` yields the six P3.8 built-in directives since
|
|
120
|
+
* `task-057-builtin-directive-templates`).
|
|
121
|
+
*
|
|
122
|
+
* FAILS CLOSED on an unrecognized `kind`: a source whose kind has no {@link INTEGRITY_POLICY} entry is
|
|
123
|
+
* reported as a failure ({@link unknownKindMessage}) rather than skipped or thrown on — it cannot be
|
|
124
|
+
* checked, so it must not be installed.
|
|
125
|
+
*
|
|
126
|
+
* Callers — `initWingfoilProject` AND `initWingfoilStorage`, both in `src/core/init.ts`; every
|
|
127
|
+
* `initStorage` write path there is one — MUST run this before writing any file: REQ-SEC-10's fit
|
|
128
|
+
* criterion requires the abort to happen "before writing partial assets". This function itself never
|
|
129
|
+
* writes or reads from disk, so calling it before the scaffold write is sufficient to satisfy that
|
|
130
|
+
* ordering.
|
|
131
|
+
*/
|
|
132
|
+
function verifyBuiltinTemplates(sources) {
|
|
133
|
+
for (const source of sources) {
|
|
134
|
+
const policy = policyFor(source.kind);
|
|
135
|
+
if (policy === undefined) {
|
|
136
|
+
return {
|
|
137
|
+
name: source.name,
|
|
138
|
+
kind: source.kind,
|
|
139
|
+
message: unknownKindMessage(source.name, String(source.kind)),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (!policy.isValid(source)) {
|
|
143
|
+
return { name: source.name, kind: source.kind, message: policy.message(source.name) };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=builtin-integrity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtin-integrity.js","sourceRoot":"","sources":["../../src/core/builtin-integrity.ts"],"names":[],"mappings":";;AAsJA,wDAiBC;AAvKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,iDAA4D;AAC5D,wCAAsG;AACtG,8CAAyD;AACzD,+CAA8C;AAe9C;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,MAA6B;IAC3D,MAAM,eAAe,GAAG,IAAA,4BAAkB,EAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC3D,IAAI,eAAe,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAA,sBAAS,EAAC,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACrD,IAAA,0BAAa,EAAC,6BAAoB,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,qBAAqB,CAAC,MAA6B;IAC1D,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAA,sBAAS,EAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACpD,IAAA,0BAAa,EAAC,iBAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAQD;;;;;;;;;GASG;AACH,MAAM,gBAAgB,GAA2D;IAC/E,SAAS,EAAE;QACT,OAAO,EAAE,sBAAsB;QAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,uDAAuD,IAAI,EAAE;KACjF;IACD,QAAQ,EAAE;QACR,OAAO,EAAE,qBAAqB;QAC9B,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,uCAAuC,IAAI,EAAE;KACjE;CACF,CAAC;AAEF;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,IAAY,EAAE,IAAY;IACpD,OAAO,6CAA6C,IAAI,wBAAwB,IAAI,IAAI,CAAC;AAC3F,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,SAAS,CAAC,IAAyB;IAC1C,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3G,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAgB,sBAAsB,CACpC,OAAyC;IAEzC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO;gBACL,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,OAAO,EAAE,kBAAkB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;aAC9D,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACxF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type CoreResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Refuse, before anything on disk changes, a target whose **real** path lies outside the project
|
|
4
|
+
* root.
|
|
5
|
+
*
|
|
6
|
+
* Resolution is the point. A prefix check over `relativePath` cannot see this: the path this bug is
|
|
7
|
+
* about — `.wingfoil/directives/custom/legacy-rule.md` under a `custom` that is a symlink to another
|
|
8
|
+
* directory — is inside the project in every segment and outside it on the filesystem. So the check
|
|
9
|
+
* resolves first and compares afterwards.
|
|
10
|
+
*
|
|
11
|
+
* Returns a `VALIDATION` `CoreResult.error`, which `exitCodeForError` maps to exit **1**: a
|
|
12
|
+
* well-formed invocation refused on the state of the repository, never the `2` that
|
|
13
|
+
* `spec-005-cli-command-contract` §1 reserves for a malformed one. The message names both spellings
|
|
14
|
+
* of the path — the one the operator typed against and the one it really resolves to — because the
|
|
15
|
+
* two differing is the entire finding, and it deliberately carries no child-process text
|
|
16
|
+
* (`bug-071`/`bug-093`).
|
|
17
|
+
*
|
|
18
|
+
* **What it answers, and what it does not.** This decides where the path *leads*: the target's
|
|
19
|
+
* parent is real-resolved and its own name is kept, which is right for a deletion and insufficient
|
|
20
|
+
* for a write ({@link resolveRealPathInRoot}'s own doc, `bug-120`). A caller that is about to
|
|
21
|
+
* **write** asks {@link requireConfinedWriteTarget} instead, which adds the second question this one
|
|
22
|
+
* does not ask.
|
|
23
|
+
*
|
|
24
|
+
* @param root - Project root.
|
|
25
|
+
* @param relativePath - Root-relative POSIX path of the file the operation is about to touch.
|
|
26
|
+
* @param action - The verb for the refusal's first clause, e.g. `'remove'` → `cannot remove '<path>'`
|
|
27
|
+
* (P3.3's own message shape, shared with `requireCustomAsset`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function requireConfinedTarget(root: string, relativePath: string, action: string): CoreResult<void>;
|
|
30
|
+
/**
|
|
31
|
+
* {@link requireConfinedTarget} plus the question a **write** has to ask and a delete must not: the
|
|
32
|
+
* target's own name may not be a symbolic link (task-106,
|
|
33
|
+
* `bug-120-a-symlinked-document-leaf-is-followed-by-the-write`).
|
|
34
|
+
*
|
|
35
|
+
* `unlinkSync` acts on a link; `writeFileSync` follows it. So the parent-resolved, leaf-unresolved
|
|
36
|
+
* shape that makes a symlinked directive file removable (`bug-044`'s benign case) lets a write land
|
|
37
|
+
* wherever the link points — measured on the four Memory transition verbs, which rewrote a linked
|
|
38
|
+
* document *outside the project root* and then failed with git's own text (`bug-120` D2). The
|
|
39
|
+
* asymmetry is therefore read per verb, which is `dl-086`'s ground and is argued there rather than
|
|
40
|
+
* here: resolve the parent everywhere, refuse a symlinked leaf where the syscall would follow it.
|
|
41
|
+
*
|
|
42
|
+
* Order matters and is the confinement one first: a path that leaves the project is reported as
|
|
43
|
+
* leaving the project, whatever its leaf is, so the operator reads the boundary finding and not a
|
|
44
|
+
* narrower one that happens to fire first. Both refusals are `VALIDATION` → exit `1`, and the
|
|
45
|
+
* symlink sentence is {@link symlinkTargetRefusal}, shared verbatim with the `StorageError`
|
|
46
|
+
* `memory add` raises through `resolveConfinedMemoryPath`, so the two surfaces cannot drift.
|
|
47
|
+
*
|
|
48
|
+
* The TOCTOU window between this check and the write is real and is not closable on a path-based
|
|
49
|
+
* API — Node's `fs` exposes no `O_NOFOLLOW` (see {@link targetIsSymlink}). This turns a routine,
|
|
50
|
+
* self-inflicted loss into a refusal; it is not an adversarial defence.
|
|
51
|
+
*
|
|
52
|
+
* @param root - Project root.
|
|
53
|
+
* @param relativePath - Root-relative POSIX path of the file the operation is about to write.
|
|
54
|
+
* @param action - The verb for the refusal's first clause, e.g. `'write'` → `cannot write '<path>'`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function requireConfinedWriteTarget(root: string, relativePath: string, action: string): CoreResult<void>;
|
|
57
|
+
//# sourceMappingURL=confinement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confinement.d.ts","sourceRoot":"","sources":["../../src/core/confinement.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,UAAU,CAAC,IAAI,CAAC,CAUlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,UAAU,CAAC,IAAI,CAAC,CAKlB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireConfinedTarget = requireConfinedTarget;
|
|
4
|
+
exports.requireConfinedWriteTarget = requireConfinedWriteTarget;
|
|
5
|
+
/**
|
|
6
|
+
* Project-root confinement as a mutating-op **pre-flight** (REQ-SEC-06 —
|
|
7
|
+
* `task-102`, `bug-044-symlinked-directives-custom-escapes-confinement`; extended to the write
|
|
8
|
+
* paths' own question by `task-106`, `bug-120`).
|
|
9
|
+
*
|
|
10
|
+
* The boundary is decided once, in `src/storage/confinement.ts`; this is the thin layer that turns
|
|
11
|
+
* that answer into the `CoreResult` a core operation returns, exactly as `requireGitIdentity`
|
|
12
|
+
* (REQ-SEC-01) and `requireCustomAsset` (REQ-SEC-07) do for their own rules. It lives in `src/core`
|
|
13
|
+
* for their reason too: the rule crosscuts pillars, and the CLI and MCP surfaces must enforce it
|
|
14
|
+
* identically because they run the same function (REQ-SYS-05).
|
|
15
|
+
*
|
|
16
|
+
* Two entry points, because a delete and a write do not ask the same thing:
|
|
17
|
+
* {@link requireConfinedTarget} for an operation whose syscall acts on a link (`unlinkSync`), and
|
|
18
|
+
* {@link requireConfinedWriteTarget} for one whose syscall follows it (`writeFileSync`).
|
|
19
|
+
*
|
|
20
|
+
* **Why a pre-flight and not a check at the write.** `bug-044`'s whole defect is order. The shipped
|
|
21
|
+
* `directive remove` unlinked the outside file and only then failed, on `git add`, with a raw
|
|
22
|
+
* `execFileSync` message — so the operator lost a file, got git's text instead of a mapped refusal,
|
|
23
|
+
* and no commit recorded any of it. A check that runs after the deletion reports a loss it could
|
|
24
|
+
* have prevented; the refusal has to land before the filesystem is touched at all.
|
|
25
|
+
*
|
|
26
|
+
* **Baseline.** This guard resolves against the **working tree**, not `HEAD`, and that is deliberate
|
|
27
|
+
* — a departure from the `command-baseline` directive's read half, argued (as that directive
|
|
28
|
+
* requires) in `task-102`'s Execution Notes and in the decision-log filed from them, never settled
|
|
29
|
+
* here. The one-line reason: what this predicts is where `unlinkSync` — or, on the write path,
|
|
30
|
+
* `writeFileSync` — will land, and both follow the symlinks that are on disk, not the ones a commit
|
|
31
|
+
* records.
|
|
32
|
+
*/
|
|
33
|
+
const node_path_1 = require("node:path");
|
|
34
|
+
const confinement_1 = require("../storage/confinement");
|
|
35
|
+
const types_1 = require("./types");
|
|
36
|
+
/**
|
|
37
|
+
* Refuse, before anything on disk changes, a target whose **real** path lies outside the project
|
|
38
|
+
* root.
|
|
39
|
+
*
|
|
40
|
+
* Resolution is the point. A prefix check over `relativePath` cannot see this: the path this bug is
|
|
41
|
+
* about — `.wingfoil/directives/custom/legacy-rule.md` under a `custom` that is a symlink to another
|
|
42
|
+
* directory — is inside the project in every segment and outside it on the filesystem. So the check
|
|
43
|
+
* resolves first and compares afterwards.
|
|
44
|
+
*
|
|
45
|
+
* Returns a `VALIDATION` `CoreResult.error`, which `exitCodeForError` maps to exit **1**: a
|
|
46
|
+
* well-formed invocation refused on the state of the repository, never the `2` that
|
|
47
|
+
* `spec-005-cli-command-contract` §1 reserves for a malformed one. The message names both spellings
|
|
48
|
+
* of the path — the one the operator typed against and the one it really resolves to — because the
|
|
49
|
+
* two differing is the entire finding, and it deliberately carries no child-process text
|
|
50
|
+
* (`bug-071`/`bug-093`).
|
|
51
|
+
*
|
|
52
|
+
* **What it answers, and what it does not.** This decides where the path *leads*: the target's
|
|
53
|
+
* parent is real-resolved and its own name is kept, which is right for a deletion and insufficient
|
|
54
|
+
* for a write ({@link resolveRealPathInRoot}'s own doc, `bug-120`). A caller that is about to
|
|
55
|
+
* **write** asks {@link requireConfinedWriteTarget} instead, which adds the second question this one
|
|
56
|
+
* does not ask.
|
|
57
|
+
*
|
|
58
|
+
* @param root - Project root.
|
|
59
|
+
* @param relativePath - Root-relative POSIX path of the file the operation is about to touch.
|
|
60
|
+
* @param action - The verb for the refusal's first clause, e.g. `'remove'` → `cannot remove '<path>'`
|
|
61
|
+
* (P3.3's own message shape, shared with `requireCustomAsset`).
|
|
62
|
+
*/
|
|
63
|
+
function requireConfinedTarget(root, relativePath, action) {
|
|
64
|
+
const resolved = (0, confinement_1.resolveRealPathInRoot)(root, relativePath);
|
|
65
|
+
if (resolved.within)
|
|
66
|
+
return (0, types_1.coreOk)(undefined);
|
|
67
|
+
return (0, types_1.coreErr)({
|
|
68
|
+
code: 'VALIDATION',
|
|
69
|
+
message: `cannot ${action} '${relativePath}': it resolves to '${resolved.real}', outside the project root. ` +
|
|
70
|
+
'Nothing has been written or deleted — this check runs before the operation touches the filesystem; ' +
|
|
71
|
+
'check whether a directory on the way to it is a symlink.',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* {@link requireConfinedTarget} plus the question a **write** has to ask and a delete must not: the
|
|
76
|
+
* target's own name may not be a symbolic link (task-106,
|
|
77
|
+
* `bug-120-a-symlinked-document-leaf-is-followed-by-the-write`).
|
|
78
|
+
*
|
|
79
|
+
* `unlinkSync` acts on a link; `writeFileSync` follows it. So the parent-resolved, leaf-unresolved
|
|
80
|
+
* shape that makes a symlinked directive file removable (`bug-044`'s benign case) lets a write land
|
|
81
|
+
* wherever the link points — measured on the four Memory transition verbs, which rewrote a linked
|
|
82
|
+
* document *outside the project root* and then failed with git's own text (`bug-120` D2). The
|
|
83
|
+
* asymmetry is therefore read per verb, which is `dl-086`'s ground and is argued there rather than
|
|
84
|
+
* here: resolve the parent everywhere, refuse a symlinked leaf where the syscall would follow it.
|
|
85
|
+
*
|
|
86
|
+
* Order matters and is the confinement one first: a path that leaves the project is reported as
|
|
87
|
+
* leaving the project, whatever its leaf is, so the operator reads the boundary finding and not a
|
|
88
|
+
* narrower one that happens to fire first. Both refusals are `VALIDATION` → exit `1`, and the
|
|
89
|
+
* symlink sentence is {@link symlinkTargetRefusal}, shared verbatim with the `StorageError`
|
|
90
|
+
* `memory add` raises through `resolveConfinedMemoryPath`, so the two surfaces cannot drift.
|
|
91
|
+
*
|
|
92
|
+
* The TOCTOU window between this check and the write is real and is not closable on a path-based
|
|
93
|
+
* API — Node's `fs` exposes no `O_NOFOLLOW` (see {@link targetIsSymlink}). This turns a routine,
|
|
94
|
+
* self-inflicted loss into a refusal; it is not an adversarial defence.
|
|
95
|
+
*
|
|
96
|
+
* @param root - Project root.
|
|
97
|
+
* @param relativePath - Root-relative POSIX path of the file the operation is about to write.
|
|
98
|
+
* @param action - The verb for the refusal's first clause, e.g. `'write'` → `cannot write '<path>'`.
|
|
99
|
+
*/
|
|
100
|
+
function requireConfinedWriteTarget(root, relativePath, action) {
|
|
101
|
+
const confined = requireConfinedTarget(root, relativePath, action);
|
|
102
|
+
if (!confined.ok)
|
|
103
|
+
return confined;
|
|
104
|
+
if (!(0, confinement_1.targetIsSymlink)((0, node_path_1.join)(root, relativePath)))
|
|
105
|
+
return (0, types_1.coreOk)(undefined);
|
|
106
|
+
return (0, types_1.coreErr)({ code: 'VALIDATION', message: (0, confinement_1.symlinkTargetRefusal)(action, relativePath) });
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=confinement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confinement.js","sourceRoot":"","sources":["../../src/core/confinement.ts"],"names":[],"mappings":";;AA6DA,sDAcC;AA4BD,gEASC;AAhHD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,yCAAiC;AAEjC,wDAAsG;AAEtG,mCAA2D;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAgB,qBAAqB,CACnC,IAAY,EACZ,YAAoB,EACpB,MAAc;IAEd,MAAM,QAAQ,GAAG,IAAA,mCAAqB,EAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC3D,IAAI,QAAQ,CAAC,MAAM;QAAE,OAAO,IAAA,cAAM,EAAO,SAAS,CAAC,CAAC;IACpD,OAAO,IAAA,eAAO,EAAC;QACb,IAAI,EAAE,YAAY;QAClB,OAAO,EACL,UAAU,MAAM,KAAK,YAAY,sBAAsB,QAAQ,CAAC,IAAI,+BAA+B;YACnG,qGAAqG;YACrG,0DAA0D;KAC7D,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,SAAgB,0BAA0B,CACxC,IAAY,EACZ,YAAoB,EACpB,MAAc;IAEd,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IACnE,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,QAAQ,CAAC;IAClC,IAAI,CAAC,IAAA,6BAAe,EAAC,IAAA,gBAAI,EAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QAAE,OAAO,IAAA,cAAM,EAAO,SAAS,CAAC,CAAC;IAC/E,OAAO,IAAA,eAAO,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAA,kCAAoB,EAAC,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;AAC9F,CAAC"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent execution context assembly (task-037-role-task-scoped-context, REQ-STATE-05) — the
|
|
3
|
+
* `directive-loader` + a minimal `context-builder` slice of
|
|
4
|
+
* `spec-012-context-loader-relevance-filtering` (§1 "placement & responsibilities", §5 "Directive
|
|
5
|
+
* resolution"). Satisfies REQ-STATE-05's Fit Criterion, verbatim: "The assembled context object
|
|
6
|
+
* exposes separate `dna`, `memory`, `directives` sections; it contains 100% of the role's assigned
|
|
7
|
+
* directives and 0 directives of other roles."
|
|
8
|
+
*
|
|
9
|
+
* **Archived elements are excluded** (REQ-STATE-06, the `{deprecated, superseded}` set ratified by
|
|
10
|
+
* `dl-028-archived-states-excluded-from-context`): {@link assembleExecutionContext} drops a resolved
|
|
11
|
+
* element whose `status` is archived, through the shared `isArchivedStatus` primitive rather than any
|
|
12
|
+
* local notion of "archived". This closed `bug-010-deprecated-reaches-agent-context` — which this
|
|
13
|
+
* header previously recorded as an open gap — together with the `wingfoil://memory/{type}` collection
|
|
14
|
+
* Resource, the other surface that bug covered (`task-069-fix-archived-excluded-from-agent-context`).
|
|
15
|
+
*
|
|
16
|
+
* Still deliberately NOT the full spec-012 pipeline: no `ContextRequest`/`stateRef` pinning, no tiered
|
|
17
|
+
* (T1–T4) Memory relevance ranking, no bounding caps, and no canonical byte-for-byte Markdown
|
|
18
|
+
* serialization (§7). Those are split across sibling Wave-1 v0.2 tasks
|
|
19
|
+
* (task-035-bounded-context-relevance REQ-PERF-05, task-038-deprecated-excluded-from-context
|
|
20
|
+
* REQ-STATE-06) and v0.3 (`REQ-SYS-07`, `REQ-PERF-01`, `REQ-STATE-09`, the real `agent execute`
|
|
21
|
+
* CLI/MCP surface) — see this task's Execution Notes for the full scope decision. This module wraps
|
|
22
|
+
* only existing, already-shipped primitives: `loadDirectives`/`loadRolesYaml` (`./loaders`),
|
|
23
|
+
* `findMemoryDocumentByTypeAndId` and `isArchivedStatus` (`../memory`, task-011/task-035) — no
|
|
24
|
+
* scan/parse logic and no second status predicate is reimplemented here.
|
|
25
|
+
*/
|
|
26
|
+
import { type MemoryDocumentSummary } from '../memory';
|
|
27
|
+
import type { MemoryYaml } from '../memory/schema';
|
|
28
|
+
import type { RolesYaml } from '../directives/schema';
|
|
29
|
+
import type { DnaYaml } from '../dna/schema';
|
|
30
|
+
import type { DirectiveFile } from './loaders';
|
|
31
|
+
/**
|
|
32
|
+
* The outcome of {@link resolveRoleDirectives}: the resolved directive files **and** any operator
|
|
33
|
+
* diagnostics produced while resolving them.
|
|
34
|
+
*
|
|
35
|
+
* `warnings` is returned rather than written to `stderr` (or to a logger) on purpose: this is a
|
|
36
|
+
* context-building path, where REQ-SYS-07 requires the output to be a pure function of the inputs.
|
|
37
|
+
* A returned array keeps the diagnostic deterministic, unit-testable without capturing process
|
|
38
|
+
* streams, and free for the eventual CLI/MCP surface (`task-055-auto-load-directives-by-role`) to
|
|
39
|
+
* render however that surface renders warnings.
|
|
40
|
+
*/
|
|
41
|
+
export interface RoleDirectiveResolution {
|
|
42
|
+
/** Deduplicated by directive id and sorted ascending by id — see {@link resolveRoleDirectives}. */
|
|
43
|
+
readonly directives: readonly DirectiveFile[];
|
|
44
|
+
/** Operator diagnostics, in a fixed order. Empty when the role's bindings are unremarkable. */
|
|
45
|
+
readonly warnings: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The outcome of {@link selectDirectivesById}: one winning file per directive id, and one shadow
|
|
49
|
+
* warning per id that more than one file defines.
|
|
50
|
+
*/
|
|
51
|
+
export interface DirectiveSelection {
|
|
52
|
+
/** Winning file per directive id, keyed and iterated in ascending id order. */
|
|
53
|
+
readonly byId: ReadonlyMap<string, DirectiveFile>;
|
|
54
|
+
/** `directive '<id>' defined in <path>, <path>; using <winner>` — one per shadowed id, ascending by id. */
|
|
55
|
+
readonly warnings: readonly string[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Deduplicate `directiveFiles` by `frontmatter.id` — the one place WingFoil decides which of several
|
|
59
|
+
* same-id directive files is in force. Both {@link resolveRoleDirectives} (context assembly) and the
|
|
60
|
+
* `directives list` payload (`./directives-list.ts`, `dl-042-directives-list-output-contract` A) call
|
|
61
|
+
* this, so the rule the listing *reports* is the rule context assembly *applies*.
|
|
62
|
+
*
|
|
63
|
+
* - Precedence: `custom/` wins over `built-in/`, then smallest path (see `compareDirectivePrecedence`).
|
|
64
|
+
* - A shadowed file is **reported, never silently dropped** (dl-037 B.1): one warning per shadowed id,
|
|
65
|
+
* naming every file that defines it (in precedence-independent path order) and the winner.
|
|
66
|
+
* - `ids`, when given, restricts selection (and warnings) to those directive ids.
|
|
67
|
+
*/
|
|
68
|
+
export declare function selectDirectivesById(directiveFiles: readonly DirectiveFile[], ids?: ReadonlySet<string>): DirectiveSelection;
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the directives bound to `role` — its own `roles.yaml` `assignments[role]` entries plus every
|
|
71
|
+
* `global` directive — per `spec-012-context-loader-relevance-filtering` §5 and
|
|
72
|
+
* `dl-029-role-with-no-directive-assignments` (`ready`, option (c)).
|
|
73
|
+
*
|
|
74
|
+
* Behaviour, point by point:
|
|
75
|
+
*
|
|
76
|
+
* - **Globals are unconditional** (spec-012 §5). Every role gets them, including a role with no
|
|
77
|
+
* bindings of its own — globals exist precisely so that no role can be configured out of
|
|
78
|
+
* `security-secrets` (dl-029, Rationale).
|
|
79
|
+
* - **A role that contributes no assignments of its own** — absent from `assignments`, or bound to an
|
|
80
|
+
* explicitly empty list — resolves to exactly the globals **and** produces the warning
|
|
81
|
+
* `no directives assigned to role '<role>'`. This is dl-029's ratified hybrid, and it is the
|
|
82
|
+
* behaviour the edge scenario of `p3-directives/P3.6-auto-load-by-role.feature` specifies
|
|
83
|
+
* ("the agent context contains only the global directives" + that warning). Never an error.
|
|
84
|
+
* Role-name lookup reads **own** properties only, so a role named after an `Object.prototype`
|
|
85
|
+
* member (`toString`, `constructor`, …) is treated as unbound like any other unknown role.
|
|
86
|
+
* - **Deduplicated by directive id, `custom/` winning over `built-in/`** (spec-012 §5, dl-037 A.1),
|
|
87
|
+
* through the shared {@link selectDirectivesById}; each shadowed id the role is bound to adds a
|
|
88
|
+
* warning naming the files and the winner (dl-037 B.1).
|
|
89
|
+
* - **A dangling binding is reported** — an id bound to the role (own assignment or global) with no
|
|
90
|
+
* file in `directiveFiles` adds `directive '<id>' bound to role '<role>' has no directive file`
|
|
91
|
+
* (`dl-042-directives-list-output-contract` D). It is still not an error: the rest resolves.
|
|
92
|
+
* - **Sorted ascending by directive id** (spec-012 §5: "never rely on `roles.yaml` listing order or
|
|
93
|
+
* file-system enumeration order", REQ-SYS-07).
|
|
94
|
+
* - `warnings` has a fixed order: the no-assignments warning, then dangling ids ascending, then
|
|
95
|
+
* shadowed ids ascending.
|
|
96
|
+
*
|
|
97
|
+
* This is REQ-STATE-05's Fit Criterion made concrete: for any two distinct roles with disjoint
|
|
98
|
+
* `assignments`, the resolved sets are disjoint too (100% of one role's directives, 0% of the other's).
|
|
99
|
+
*/
|
|
100
|
+
export declare function resolveRoleDirectives(directiveFiles: readonly DirectiveFile[], rolesYaml: RolesYaml, role: string): RoleDirectiveResolution;
|
|
101
|
+
/** The active Memory element an execution context is scoped to (spec-012 §2 `ContextRequest.element`,
|
|
102
|
+
* narrowed here to just `type`/`id` — this task does not implement the full `ContextRequest` shape). */
|
|
103
|
+
export interface ExecutionContextElement {
|
|
104
|
+
readonly type: string;
|
|
105
|
+
readonly id: string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The assembled execution context (REQ-STATE-05 Fit Criterion, P5.4.4 "a context object containing
|
|
109
|
+
* distinct DNA, Memory, and Directives sections", "each section is individually addressable"): three
|
|
110
|
+
* plain, independently-readable properties — `context.dna`, `context.memory`,
|
|
111
|
+
* `context.directives` — never merged into one flat structure.
|
|
112
|
+
*/
|
|
113
|
+
export interface ExecutionContext {
|
|
114
|
+
/** The full parsed `dna.yaml` (P5.4.4: DNA section — this task does not sub-select DNA sections by
|
|
115
|
+
* scope, unlike spec-012 §4's full `dna-loader`; that selection logic is out of this task's scope). */
|
|
116
|
+
readonly dna: DnaYaml;
|
|
117
|
+
/** This role's assigned directives + global, resolved by {@link resolveRoleDirectives}. */
|
|
118
|
+
readonly directives: readonly DirectiveFile[];
|
|
119
|
+
/** Memory relevant to `element` — today, exactly the element's own document (0 or 1 entries), and
|
|
120
|
+
* empty when that document is archived (REQ-STATE-06; see {@link assembleExecutionContext}); the
|
|
121
|
+
* full T1–T4 tiered relevance expansion is task-035/038/v0.3's scope (see the module doc comment). */
|
|
122
|
+
readonly memory: readonly MemoryDocumentSummary[];
|
|
123
|
+
/** Operator diagnostics gathered during assembly — currently only
|
|
124
|
+
* {@link RoleDirectiveResolution.warnings}: no-assignments (dl-029), dangling binding (dl-042 D),
|
|
125
|
+
* shadowed directive (dl-037 B.1). Diagnostics *about* the context, not content
|
|
126
|
+
* *of* it: spec-012 §7's canonical envelope has no warnings section, so this never enters the
|
|
127
|
+
* serialized payload. */
|
|
128
|
+
readonly warnings: readonly string[];
|
|
129
|
+
}
|
|
130
|
+
/** Inputs to {@link assembleExecutionContext} — every pillar's config, already loaded by its own
|
|
131
|
+
* loader (`./loaders.ts`), plus the `role`/`element` the context is being assembled for. Nothing here
|
|
132
|
+
* reads the filesystem itself beyond resolving `element` via `findMemoryDocumentByTypeAndId`. */
|
|
133
|
+
export interface ExecutionContextInputs {
|
|
134
|
+
/** Project root the Memory element lookup resolves against (same root the pillar loaders were
|
|
135
|
+
* called with). */
|
|
136
|
+
readonly root: string;
|
|
137
|
+
readonly dna: DnaYaml;
|
|
138
|
+
readonly memoryYaml: MemoryYaml;
|
|
139
|
+
readonly directiveFiles: readonly DirectiveFile[];
|
|
140
|
+
readonly rolesYaml: RolesYaml;
|
|
141
|
+
readonly role: string;
|
|
142
|
+
readonly element: ExecutionContextElement;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Assemble a role- and task-scoped {@link ExecutionContext} (REQ-STATE-05) from already-loaded pillar
|
|
146
|
+
* config. Given the same inputs and unchanged on-disk Memory content, the output is deep-equal across
|
|
147
|
+
* calls: the function itself introduces no wall-clock, randomness, or unordered iteration
|
|
148
|
+
* (REQ-SYS-07's discipline, applied to this narrower slice per REQ-STATE-05's own rationale "crisp,
|
|
149
|
+
* low-noise context; determinism"), and `directives` is totally ordered by
|
|
150
|
+
* {@link resolveRoleDirectives}.
|
|
151
|
+
*
|
|
152
|
+
* `memory` resolves `element` via `findMemoryDocumentByTypeAndId`
|
|
153
|
+
* (`task-011-mcp-resources-read-only`). Two properties of that primitive are inherited here and are
|
|
154
|
+
* worth stating plainly:
|
|
155
|
+
*
|
|
156
|
+
* - It is **not** a single addressed read. It walks the Memory document paths derived from
|
|
157
|
+
* `memoryYaml` in sorted order and YAML-parses each document's frontmatter until one matches
|
|
158
|
+
* `element.type`/`element.id`, so cost grows with the size of the Memory tree (bounding that is
|
|
159
|
+
* `task-035-bounded-context-relevance`'s REQ-PERF-05 scope, not this task's).
|
|
160
|
+
* - It therefore **can throw**. An `element` that matches nothing yields an empty `memory` array
|
|
161
|
+
* rather than an error — an unresolvable element is the caller's concern to surface — but a
|
|
162
|
+
* `ValidationError` (`E_YAML_PARSE_ERROR`) propagates out of assembly if any document visited
|
|
163
|
+
* during that walk has unparseable frontmatter. This function adds no `try`/`catch` of its own.
|
|
164
|
+
*
|
|
165
|
+
* `memory` **is** filtered by document status, in one direction only: a resolved element whose
|
|
166
|
+
* `status` is archived — `{deprecated, superseded}`, per the shared {@link isArchivedStatus}
|
|
167
|
+
* (`../memory`, the set ratified by `dl-028-archived-states-excluded-from-context`) — is dropped,
|
|
168
|
+
* leaving `memory` empty. That is REQ-STATE-06's Fit Criterion as amended: "a `deprecated` or
|
|
169
|
+
* `superseded` document never appears in an assembled agent context". The filter is applied to the
|
|
170
|
+
* lookup's *result* rather than pushed into `findMemoryDocumentByTypeAndId`, because that same
|
|
171
|
+
* primitive also serves `wingfoil://memory/{type}/{id}`, where explicit retrieval of archived content
|
|
172
|
+
* must keep working.
|
|
173
|
+
*
|
|
174
|
+
* `draft` is deliberately **not** excluded here, unlike in `./relevance.ts`. That module filters
|
|
175
|
+
* *candidate* documents for relevance (spec-012 §6, which does bar `draft` from a context); this
|
|
176
|
+
* function resolves the **subject** element the context is being assembled for (spec-012 §3's
|
|
177
|
+
* `resolve-element` stage), and blanking the context for a task still in `draft` would defeat the
|
|
178
|
+
* point of assembling it. dl-028 ratified the archived set as `{deprecated, superseded}` and did not
|
|
179
|
+
* put `draft` in it.
|
|
180
|
+
*
|
|
181
|
+
* An archived element produces no warning: {@link ExecutionContext.warnings} carries
|
|
182
|
+
* directive-resolution diagnostics only (dl-029, dl-037, dl-042), and adding an unratified entry would change a payload
|
|
183
|
+
* REQ-SYS-07 governs. An archived element is therefore indistinguishable here from an unresolvable
|
|
184
|
+
* one — both yield `memory: []`.
|
|
185
|
+
*/
|
|
186
|
+
export declare function assembleExecutionContext(inputs: ExecutionContextInputs): ExecutionContext;
|
|
187
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/core/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAmD,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACxG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C;;;;;;;;;GASG;AACH,MAAM,WAAW,uBAAuB;IACtC,mGAAmG;IACnG,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAWD;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAClD,2GAA2G;IAC3G,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAoBD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,SAAS,aAAa,EAAE,EACxC,GAAG,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACxB,kBAAkB,CAuBpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,SAAS,aAAa,EAAE,EACxC,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,GACX,uBAAuB,CAczB;AAED;wGACwG;AACxG,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B;2GACuG;IACvG,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,2FAA2F;IAC3F,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C;;0GAEsG;IACtG,QAAQ,CAAC,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAClD;;;;6BAIyB;IACzB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED;;iGAEiG;AACjG,MAAM,WAAW,sBAAsB;IACrC;uBACmB;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,SAAS,aAAa,EAAE,CAAC;IAClD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CASzF"}
|