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,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildCliCommands = buildCliCommands;
|
|
4
|
+
exports.listRegisteredCliCommands = listRegisteredCliCommands;
|
|
5
|
+
const registry_1 = require("../core/registry");
|
|
6
|
+
const exit_code_1 = require("../core/exit-code");
|
|
7
|
+
const error_1 = require("./error");
|
|
8
|
+
const exit_1 = require("./exit");
|
|
9
|
+
const output_1 = require("./output");
|
|
10
|
+
/**
|
|
11
|
+
* Derive one `CliCommand` per operation in `modules`, in `enumerateOperations`'s deterministic
|
|
12
|
+
* order (REQ-SYS-07). The verb is derived via `deriveVerb` (spec-006 §5).
|
|
13
|
+
*/
|
|
14
|
+
function buildCliCommands(modules, options) {
|
|
15
|
+
return (0, registry_1.enumerateOperations)(modules).map(({ module, operation }) => {
|
|
16
|
+
const verb = (0, registry_1.deriveVerb)(module.name, operation.name);
|
|
17
|
+
return {
|
|
18
|
+
noun: module.name,
|
|
19
|
+
verb,
|
|
20
|
+
mutates: operation.mutates,
|
|
21
|
+
flags: operation.flags,
|
|
22
|
+
options: operation.options,
|
|
23
|
+
run: async (formatValue, positionals, flags, optionValues) => {
|
|
24
|
+
if (!(0, output_1.isValidFormat)(formatValue)) {
|
|
25
|
+
(0, exit_1.exitWith)(2, `error: invalid --format value "${formatValue}", expected one of: console, json, yaml`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const format = formatValue;
|
|
29
|
+
let result;
|
|
30
|
+
try {
|
|
31
|
+
// `resolveRoot()` / `buildParams()` run INSIDE the try so an ambient failure — e.g. a
|
|
32
|
+
// `StorageError` from resolving the git root outside a WingFoil project — is rendered
|
|
33
|
+
// through the spec-005 §1 single-exit path (emitError + exitWith), never escaping as an
|
|
34
|
+
// uncaught throw that a top-level handler would stack-dump (bug-002-cli-error-stack-dump).
|
|
35
|
+
const params = options.buildParams({
|
|
36
|
+
moduleName: module.name,
|
|
37
|
+
operationName: operation.name,
|
|
38
|
+
root: options.resolveRoot(),
|
|
39
|
+
// `positional` stays the first positional (single-positional read ops read it); `positionals`
|
|
40
|
+
// is the full list a multi-input op (`dnaSet`) reads (task-025-implement-dna-set).
|
|
41
|
+
positional: positionals?.[0],
|
|
42
|
+
positionals,
|
|
43
|
+
flags,
|
|
44
|
+
// Value-bearing `--{name} <value>` options a data-mutating op reads (task-020's `memoryAdd`
|
|
45
|
+
// reads `type`/`title`/`tags`). Undefined for every op declaring none.
|
|
46
|
+
options: optionValues,
|
|
47
|
+
});
|
|
48
|
+
result = await operation.fn(params);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// Core owns exit-code selection for a thrown error too (spec-008 Consequences): a UsageError
|
|
52
|
+
// (a malformed argument, e.g. `dna set`'s invalid key path) surfaces as exit 2, everything
|
|
53
|
+
// else as 1 — never a bare crash (bug-002-cli-error-stack-dump). task-025-implement-dna-set.
|
|
54
|
+
const { reason, exitCode } = (0, exit_code_1.exitCodeForThrow)(error);
|
|
55
|
+
(0, error_1.emitError)(reason, { format });
|
|
56
|
+
(0, exit_1.exitWith)(exitCode);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Render the outcome, then terminate through the single exit function with the code core
|
|
60
|
+
// selects for this result (`0` success / `1` logic error) — the CLI does not re-decide the
|
|
61
|
+
// `0`/`1` mapping (task-012, spec-005 §1). `2` (usage error) is handled above, pre-core.
|
|
62
|
+
if (result.ok) {
|
|
63
|
+
process.stdout.write((0, output_1.renderSuccess)(result.value, format));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
(0, error_1.emitError)(result.error.message, { format });
|
|
67
|
+
}
|
|
68
|
+
(0, exit_1.exitWith)((0, exit_code_1.exitCodeForResult)(result));
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Every `"{noun} {verb}"` command derived from `commands`, sorted (REQ-SYS-07) — the CLI-side
|
|
75
|
+
* enumeration the REQ-SYS-05 parity test (`test/core/parity.test.ts`) diffs against the MCP Tool
|
|
76
|
+
* list. A flat, no-verb command (`verb === ''`, task-028-implement-paths-category) renders as the
|
|
77
|
+
* bare noun (`"paths"`), not `"paths "` with a trailing space.
|
|
78
|
+
*/
|
|
79
|
+
function listRegisteredCliCommands(commands) {
|
|
80
|
+
return commands.map((command) => (command.verb ? `${command.noun} ${command.verb}` : command.noun)).sort();
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=registrar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registrar.js","sourceRoot":"","sources":["../../src/cli/registrar.ts"],"names":[],"mappings":";;AA2EA,4CA+DC;AAQD,8DAEC;AAlID,+CAAmE;AAEnE,iDAAwE;AAExE,mCAAoC;AACpC,iCAAkC;AAClC,qCAAwD;AA+CxD;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,OAA8B,EAAE,OAA6B;IAC5F,OAAO,IAAA,8BAAmB,EAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE;QAChE,MAAM,IAAI,GAAG,IAAA,qBAAU,EAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACrD,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI;YACJ,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,KAAK,EAAE,SAAS,CAAC,KAAK;YACtB,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,GAAG,EAAE,KAAK,EACR,WAAmB,EACnB,WAA+B,EAC/B,KAAyC,EACzC,YAA+C,EAC/C,EAAE;gBACF,IAAI,CAAC,IAAA,sBAAa,EAAC,WAAW,CAAC,EAAE,CAAC;oBAChC,IAAA,eAAQ,EAAC,CAAC,EAAE,kCAAkC,WAAW,yCAAyC,CAAC,CAAC;oBACpG,OAAO;gBACT,CAAC;gBACD,MAAM,MAAM,GAAG,WAAW,CAAC;gBAE3B,IAAI,MAA2B,CAAC;gBAChC,IAAI,CAAC;oBACH,sFAAsF;oBACtF,sFAAsF;oBACtF,wFAAwF;oBACxF,2FAA2F;oBAC3F,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;wBACjC,UAAU,EAAE,MAAM,CAAC,IAAI;wBACvB,aAAa,EAAE,SAAS,CAAC,IAAI;wBAC7B,IAAI,EAAE,OAAO,CAAC,WAAW,EAAE;wBAC3B,8FAA8F;wBAC9F,mFAAmF;wBACnF,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;wBAC5B,WAAW;wBACX,KAAK;wBACL,4FAA4F;wBAC5F,uEAAuE;wBACvE,OAAO,EAAE,YAAY;qBACtB,CAAC,CAAC;oBACH,MAAM,GAAG,MAAM,SAAS,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;gBACtC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,6FAA6F;oBAC7F,2FAA2F;oBAC3F,6FAA6F;oBAC7F,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAA,4BAAgB,EAAC,KAAK,CAAC,CAAC;oBACrD,IAAA,iBAAS,EAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;oBAC9B,IAAA,eAAQ,EAAC,QAAQ,CAAC,CAAC;oBACnB,OAAO;gBACT,CAAC;gBAED,yFAAyF;gBACzF,2FAA2F;gBAC3F,yFAAyF;gBACzF,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;oBACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAA,sBAAa,EAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;gBAC5D,CAAC;qBAAM,CAAC;oBACN,IAAA,iBAAS,EAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC9C,CAAC;gBACD,IAAA,eAAQ,EAAC,IAAA,6BAAiB,EAAC,MAAM,CAAC,CAAC,CAAC;YACtC,CAAC;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAgB,yBAAyB,CAAC,QAA+B;IACvE,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC7G,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
/**
|
|
5
|
+
* The `wingfoil` CLI executable — task-007-npm-distribution (REQ-SYS-09). This is the `bin`
|
|
6
|
+
* entrypoint `package.json`'s `"bin"` field maps to (compiled to `dist/cli.js`); `npm install -g
|
|
7
|
+
* wingfoil` places a shim on `PATH` that runs this file directly (npm chmods it executable at link
|
|
8
|
+
* time — the `#!/usr/bin/env node` shebang above is what makes the shim itself runnable).
|
|
9
|
+
*
|
|
10
|
+
* It wires the real `commander` program built by `./cli/program.ts` (`buildProgram`, task-006) onto
|
|
11
|
+
* a real process invocation, exactly like `test/cli/fixtures/cli-harness.cjs` drives it against a
|
|
12
|
+
* fixture root in tests — the difference here is `resolveRoot` resolves the *real* project root via
|
|
13
|
+
* git-root detection (`./storage/git-root.ts`, spec-011-storage-layout) instead of a fixed fixture
|
|
14
|
+
* path, and the real `process.argv` is parsed instead of a synthetic one.
|
|
15
|
+
*
|
|
16
|
+
* `resolveRoot` is only invoked lazily, once per dispatched command (see `./cli/registrar.ts`'s
|
|
17
|
+
* `run`) — `--help` (Commander built-in) and `--version` (registered by `buildProgram` via
|
|
18
|
+
* `.version()`, see `./cli/program.ts` — bug-001-cli-version-flag) are both handled by `commander`
|
|
19
|
+
* before any command handler runs, so `wingfoil --help` / `wingfoil --version` exit `0`
|
|
20
|
+
* (spec-005-cli-command-contract §1, spec-008-cli-grammar §1) even outside a git repository; only an
|
|
21
|
+
* actual `<noun> <verb>` invocation needs a resolvable git root.
|
|
22
|
+
*/
|
|
23
|
+
const program_1 = require("./cli/program");
|
|
24
|
+
const core_1 = require("./core");
|
|
25
|
+
const git_root_1 = require("./storage/git-root");
|
|
26
|
+
(0, program_1.buildProgram)(core_1.CORE_MODULES, {
|
|
27
|
+
resolveRoot: () => (0, git_root_1.resolveProjectRoot)(process.cwd()),
|
|
28
|
+
// task-026's single bare `positional` (`ctx.positional`, read by `dnaShowFn`/`pathsFn` as
|
|
29
|
+
// section/category), task-025's full `positionals` list (read by `dnaSetFn` as `<key> <value>`),
|
|
30
|
+
// task-028's additive `flags` spread (e.g. `paths`'s `--list` -> `list: true`), plus task-020's
|
|
31
|
+
// value-bearing `options` record (read by `memoryAddFn` as `type`/`title`/`tags`). A command with no
|
|
32
|
+
// positional/flags/options is unaffected.
|
|
33
|
+
buildParams: (ctx) => ({ root: ctx.root, positional: ctx.positional, positionals: ctx.positionals, options: ctx.options, ...(ctx.flags ?? {}) }),
|
|
34
|
+
})
|
|
35
|
+
.then((program) => program.parseAsync(process.argv))
|
|
36
|
+
.catch((error) => {
|
|
37
|
+
// Last-resort handler for anything that escapes the per-command spec-005 exit path. Emit only the
|
|
38
|
+
// message as a single `error:` line — never `error.stack`, which would leak a stack trace and
|
|
39
|
+
// absolute internal paths from a published CLI (bug-002-cli-error-stack-dump). The normal
|
|
40
|
+
// no-git-root path is already handled cleanly inside the registrar; this guards the unexpected.
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
process.stderr.write(`error: ${message}\n`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AACA;;;;;;;;;;;;;;;;;;GAkBG;AACH,2CAA6C;AAC7C,iCAAsC;AACtC,iDAAwD;AAExD,IAAA,sBAAY,EAAC,mBAAY,EAAE;IACzB,WAAW,EAAE,GAAG,EAAE,CAAC,IAAA,6BAAkB,EAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACpD,0FAA0F;IAC1F,iGAAiG;IACjG,gGAAgG;IAChG,qGAAqG;IACrG,0CAA0C;IAC1C,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;CACjJ,CAAC;KACC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;KACnD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACxB,kGAAkG;IAClG,8FAA8F;IAC9F,0FAA0F;IAC1F,gGAAgG;IAChG,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role-based approval authority (REQ-SEC-03, adr-006-git-identity-role-based-authz —
|
|
3
|
+
* task-040-role-based-approval-authority).
|
|
4
|
+
*
|
|
5
|
+
* adr-006's second point fixes approval authority as role-based, never bound to a named person: a
|
|
6
|
+
* `.wingfoil/dna.yaml` `team.members[]` entry holds one or more `roles` from the canonical
|
|
7
|
+
* `team.roles` catalogue (`src/dna/schema.ts`'s `Team`), and only a member holding the `approver`
|
|
8
|
+
* role may approve a Memory document — uniformly across every type. There is no per-type approver
|
|
9
|
+
* role anywhere in `memory.yaml`/`dna.yaml`: every `approval: { by_role: ... }` step across
|
|
10
|
+
* `.wingfoil/workflows/custom/*.yaml` already gates on the single `approver` role, so this module
|
|
11
|
+
* does the same rather than inventing a per-type mapping no spec defines.
|
|
12
|
+
*
|
|
13
|
+
* "Who is asking" is resolved the same way `requireGitIdentity` (REQ-SEC-01) already establishes
|
|
14
|
+
* attribution: the live git identity configured at the project root (`readGitIdentity`,
|
|
15
|
+
* `./git-identity.ts`) — git identity is WingFoil's sole attribution mechanism (adr-001/adr-006), so
|
|
16
|
+
* authority is checked against the exact same "who" the resulting approval commit will record. This
|
|
17
|
+
* is why `team.agents` is deliberately NOT consulted here: an AI agent has no git identity of its own
|
|
18
|
+
* distinct from whichever human account runs the command, so there is no code-level signal this
|
|
19
|
+
* git-identity-keyed check could use to single an agent out. Structurally preventing agents from
|
|
20
|
+
* holding approval authority is instead a `dna.yaml` fact (`team.agents[].approval_authority: false`)
|
|
21
|
+
* enforced by process/governance (CLAUDE.md §4/§8: "AI agents ... never hold approval authority"), not
|
|
22
|
+
* by this predicate.
|
|
23
|
+
*
|
|
24
|
+
* **"Who is authorized" is read from the COMMITTED `dna.yaml`, never from the working tree**
|
|
25
|
+
* (task-090, `bug-079-uncommitted-dna-yaml-grants-approval-authority`). The two halves of the check
|
|
26
|
+
* therefore take deliberately different baselines, and each takes the only one it can:
|
|
27
|
+
*
|
|
28
|
+
* - *who is asking* — the live git config, which is a local setting git never commits, and which is
|
|
29
|
+
* also exactly what the resulting commit will record as its author;
|
|
30
|
+
* - *who may approve* — `HEAD`'s `.wingfoil/dna.yaml`, because an approval is evidence and evidence
|
|
31
|
+
* is worth what an independent reader can re-derive from the artefact of record. An uncommitted
|
|
32
|
+
* grant used to be enough to put `Approver: … (approver)` into a permanent commit that the
|
|
33
|
+
* repository's own record, at that very commit, contradicted.
|
|
34
|
+
*/
|
|
35
|
+
import type { DnaYaml } from '../dna/schema';
|
|
36
|
+
import { type CoreResult } from './types';
|
|
37
|
+
/** The one role that carries approval authority, uniformly across every Memory type (adr-006). */
|
|
38
|
+
export declare const APPROVER_ROLE: "approver";
|
|
39
|
+
/**
|
|
40
|
+
* The roles held by the `dna.yaml` `team.members[]` entry whose `email` matches `email`
|
|
41
|
+
* (case-insensitively — git identities are free-form and casing is not semantically meaningful),
|
|
42
|
+
* or `[]` when `email` is empty or matches no member. Pure lookup — no git/filesystem access; callers
|
|
43
|
+
* needing the live git identity go through {@link requireApprovalAuthority} or `readGitIdentity`
|
|
44
|
+
* (`./git-identity.ts`) themselves.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveMemberRoles(dna: DnaYaml, email: string): readonly string[];
|
|
47
|
+
/** Whether the `dna.yaml` team member matching `email` holds the {@link APPROVER_ROLE}. */
|
|
48
|
+
export declare function hasApproverRole(dna: DnaYaml, email: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Verify the principal identified by the live git identity at `root` holds the `approver` role — **as
|
|
51
|
+
* the committed `.wingfoil/dna.yaml` records it** — before an approval (`memory.approve`, P1.7) or a
|
|
52
|
+
* rejection (`memory.reject`, P1.8) proceeds. Returns a `CoreResult.error` (code `VALIDATION` — a
|
|
53
|
+
* failed authorization precondition, mapped to exit `1` by `exitCodeForError`, mirroring
|
|
54
|
+
* `requireGitIdentity`'s own precondition shape; `spec-005` §1 reserves `2` for a malformed
|
|
55
|
+
* *invocation*, which none of these refusals is) carrying the exact REQ-SEC-03 fit-criterion message
|
|
56
|
+
* `user not authorized to approve type '<type>'` when the git-configured email holds no `approver`
|
|
57
|
+
* role at `HEAD` (including when it is unset, or matches no `team.members` entry at all); otherwise
|
|
58
|
+
* `ok`. `typeName` is the Memory element type being approved (e.g. `task`, `adr`) — interpolated into
|
|
59
|
+
* the refusal message only, it plays no role in the authority check itself (§ module doc comment: the
|
|
60
|
+
* `approver` role is uniform across types).
|
|
61
|
+
*
|
|
62
|
+
* Three refusals, all fail-closed, all before anything is written:
|
|
63
|
+
*
|
|
64
|
+
* 1. **No authority at `HEAD`** — REQ-SEC-03's message verbatim. When the *working tree* would have
|
|
65
|
+
* granted it, a second sentence names the baseline, so a user whose editor shows them as an
|
|
66
|
+
* approver is told why the tool disagrees instead of being left to argue with the file on their
|
|
67
|
+
* screen. That diagnostic is the half of option (b) — refusing while `dna.yaml` is modified —
|
|
68
|
+
* worth keeping; the refusal itself remains a statement about the repository.
|
|
69
|
+
* 2. **No committed `dna.yaml` at all** — an untracked file, or a repository with no commits: there
|
|
70
|
+
* is no record to read, so no authority can be established.
|
|
71
|
+
* 3. **A committed `dna.yaml` that does not parse or validate** — the baseline is unreadable, which
|
|
72
|
+
* is not the same as "grants nothing", but leads to the same refusal.
|
|
73
|
+
*
|
|
74
|
+
* This function takes `root`, not a `DnaYaml`: a caller cannot hand it a parsed working-tree
|
|
75
|
+
* configuration even by accident, which is what makes the committed baseline a property of the read
|
|
76
|
+
* itself rather than of a precondition someone must remember to run (task-090, AC2).
|
|
77
|
+
*
|
|
78
|
+
* Callers run this AFTER `requireGitIdentity` (REQ-SEC-01) in the same mutating-op pre-flight
|
|
79
|
+
* sequence `dnaSetFn`/`memoryAddFn` (`src/core/index.ts`) establish — an unconfigured identity should
|
|
80
|
+
* surface as REQ-SEC-01's own message, not this one.
|
|
81
|
+
*/
|
|
82
|
+
export declare function requireApprovalAuthority(root: string, typeName: string): CoreResult<void>;
|
|
83
|
+
//# sourceMappingURL=approval-authority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-authority.d.ts","sourceRoot":"","sources":["../../src/core/approval-authority.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAK7C,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAE3D,kGAAkG;AAClG,eAAO,MAAM,aAAa,EAAG,UAAmB,CAAC;AAEjD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAKjF;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpE;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAmCzF"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APPROVER_ROLE = void 0;
|
|
4
|
+
exports.resolveMemberRoles = resolveMemberRoles;
|
|
5
|
+
exports.hasApproverRole = hasApproverRole;
|
|
6
|
+
exports.requireApprovalAuthority = requireApprovalAuthority;
|
|
7
|
+
const validation_1 = require("../validation");
|
|
8
|
+
const git_identity_1 = require("./git-identity");
|
|
9
|
+
const loaders_1 = require("./loaders");
|
|
10
|
+
const types_1 = require("./types");
|
|
11
|
+
/** The one role that carries approval authority, uniformly across every Memory type (adr-006). */
|
|
12
|
+
exports.APPROVER_ROLE = 'approver';
|
|
13
|
+
/**
|
|
14
|
+
* The roles held by the `dna.yaml` `team.members[]` entry whose `email` matches `email`
|
|
15
|
+
* (case-insensitively — git identities are free-form and casing is not semantically meaningful),
|
|
16
|
+
* or `[]` when `email` is empty or matches no member. Pure lookup — no git/filesystem access; callers
|
|
17
|
+
* needing the live git identity go through {@link requireApprovalAuthority} or `readGitIdentity`
|
|
18
|
+
* (`./git-identity.ts`) themselves.
|
|
19
|
+
*/
|
|
20
|
+
function resolveMemberRoles(dna, email) {
|
|
21
|
+
if (email.length === 0)
|
|
22
|
+
return [];
|
|
23
|
+
const normalized = email.toLowerCase();
|
|
24
|
+
const member = dna.team.members.find((candidate) => candidate.email?.toLowerCase() === normalized);
|
|
25
|
+
return member?.roles ?? [];
|
|
26
|
+
}
|
|
27
|
+
/** Whether the `dna.yaml` team member matching `email` holds the {@link APPROVER_ROLE}. */
|
|
28
|
+
function hasApproverRole(dna, email) {
|
|
29
|
+
return resolveMemberRoles(dna, email).includes(exports.APPROVER_ROLE);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether the working tree's `dna.yaml` grants `email` the `approver` role — a **diagnostic only**,
|
|
33
|
+
* never a decision. It answers one question: is the user looking at a file that says something the
|
|
34
|
+
* repository has not recorded? Any failure to read or parse it is simply "no" (`false`): an
|
|
35
|
+
* unreadable working-tree file must never change the outcome of a check whose answer comes from
|
|
36
|
+
* `HEAD`.
|
|
37
|
+
*/
|
|
38
|
+
function workingTreeWouldGrant(root, email) {
|
|
39
|
+
try {
|
|
40
|
+
return hasApproverRole((0, loaders_1.loadDnaYaml)(root), email);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Verify the principal identified by the live git identity at `root` holds the `approver` role — **as
|
|
48
|
+
* the committed `.wingfoil/dna.yaml` records it** — before an approval (`memory.approve`, P1.7) or a
|
|
49
|
+
* rejection (`memory.reject`, P1.8) proceeds. Returns a `CoreResult.error` (code `VALIDATION` — a
|
|
50
|
+
* failed authorization precondition, mapped to exit `1` by `exitCodeForError`, mirroring
|
|
51
|
+
* `requireGitIdentity`'s own precondition shape; `spec-005` §1 reserves `2` for a malformed
|
|
52
|
+
* *invocation*, which none of these refusals is) carrying the exact REQ-SEC-03 fit-criterion message
|
|
53
|
+
* `user not authorized to approve type '<type>'` when the git-configured email holds no `approver`
|
|
54
|
+
* role at `HEAD` (including when it is unset, or matches no `team.members` entry at all); otherwise
|
|
55
|
+
* `ok`. `typeName` is the Memory element type being approved (e.g. `task`, `adr`) — interpolated into
|
|
56
|
+
* the refusal message only, it plays no role in the authority check itself (§ module doc comment: the
|
|
57
|
+
* `approver` role is uniform across types).
|
|
58
|
+
*
|
|
59
|
+
* Three refusals, all fail-closed, all before anything is written:
|
|
60
|
+
*
|
|
61
|
+
* 1. **No authority at `HEAD`** — REQ-SEC-03's message verbatim. When the *working tree* would have
|
|
62
|
+
* granted it, a second sentence names the baseline, so a user whose editor shows them as an
|
|
63
|
+
* approver is told why the tool disagrees instead of being left to argue with the file on their
|
|
64
|
+
* screen. That diagnostic is the half of option (b) — refusing while `dna.yaml` is modified —
|
|
65
|
+
* worth keeping; the refusal itself remains a statement about the repository.
|
|
66
|
+
* 2. **No committed `dna.yaml` at all** — an untracked file, or a repository with no commits: there
|
|
67
|
+
* is no record to read, so no authority can be established.
|
|
68
|
+
* 3. **A committed `dna.yaml` that does not parse or validate** — the baseline is unreadable, which
|
|
69
|
+
* is not the same as "grants nothing", but leads to the same refusal.
|
|
70
|
+
*
|
|
71
|
+
* This function takes `root`, not a `DnaYaml`: a caller cannot hand it a parsed working-tree
|
|
72
|
+
* configuration even by accident, which is what makes the committed baseline a property of the read
|
|
73
|
+
* itself rather than of a precondition someone must remember to run (task-090, AC2).
|
|
74
|
+
*
|
|
75
|
+
* Callers run this AFTER `requireGitIdentity` (REQ-SEC-01) in the same mutating-op pre-flight
|
|
76
|
+
* sequence `dnaSetFn`/`memoryAddFn` (`src/core/index.ts`) establish — an unconfigured identity should
|
|
77
|
+
* surface as REQ-SEC-01's own message, not this one.
|
|
78
|
+
*/
|
|
79
|
+
function requireApprovalAuthority(root, typeName) {
|
|
80
|
+
const { email } = (0, git_identity_1.readGitIdentity)(root);
|
|
81
|
+
let committed;
|
|
82
|
+
try {
|
|
83
|
+
committed = (0, loaders_1.loadDnaYamlAtHead)(root);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (!(error instanceof validation_1.ValidationError))
|
|
87
|
+
throw error;
|
|
88
|
+
return (0, types_1.coreErr)({
|
|
89
|
+
code: 'VALIDATION',
|
|
90
|
+
message: `cannot resolve approval authority: the committed '${loaders_1.DNA_YAML_PATH}' (at HEAD) is not readable as DNA: ` +
|
|
91
|
+
`${error.message}. Approval authority is read from the committed configuration, so the committed file ` +
|
|
92
|
+
'must be valid; fix it and commit the fix, then retry.',
|
|
93
|
+
details: { issues: error.issues },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (committed === null) {
|
|
97
|
+
return (0, types_1.coreErr)({
|
|
98
|
+
code: 'VALIDATION',
|
|
99
|
+
message: `cannot resolve approval authority: '${loaders_1.DNA_YAML_PATH}' is not committed at HEAD. Approval authority is a ` +
|
|
100
|
+
'property of the repository, not of a working tree (adr-006), so an approval can only rest on roles some ' +
|
|
101
|
+
`commit records; commit '${loaders_1.DNA_YAML_PATH}' first, then retry.`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (hasApproverRole(committed, email))
|
|
105
|
+
return (0, types_1.coreOk)(undefined);
|
|
106
|
+
const uncommittedGrant = workingTreeWouldGrant(root, email)
|
|
107
|
+
? ` — the working tree's '${loaders_1.DNA_YAML_PATH}' grants it, but that change is not committed, and approval authority ` +
|
|
108
|
+
`is read from the committed configuration (adr-006); commit '${loaders_1.DNA_YAML_PATH}' first, then retry`
|
|
109
|
+
: '';
|
|
110
|
+
return (0, types_1.coreErr)({ code: 'VALIDATION', message: `user not authorized to approve type '${typeName}'${uncommittedGrant}` });
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=approval-authority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-authority.js","sourceRoot":"","sources":["../../src/core/approval-authority.ts"],"names":[],"mappings":";;;AAmDA,gDAKC;AAGD,0CAEC;AAkDD,4DAmCC;AA/GD,8CAAgD;AAEhD,iDAAiD;AACjD,uCAA0E;AAC1E,mCAA2D;AAE3D,kGAAkG;AACrF,QAAA,aAAa,GAAG,UAAmB,CAAC;AAEjD;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,GAAY,EAAE,KAAa;IAC5D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACvC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,UAAU,CAAC,CAAC;IACnG,OAAO,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED,2FAA2F;AAC3F,SAAgB,eAAe,CAAC,GAAY,EAAE,KAAa;IACzD,OAAO,kBAAkB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,qBAAa,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,qBAAqB,CAAC,IAAY,EAAE,KAAa;IACxD,IAAI,CAAC;QACH,OAAO,eAAe,CAAC,IAAA,qBAAW,EAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,SAAgB,wBAAwB,CAAC,IAAY,EAAE,QAAgB;IACrE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAA,8BAAe,EAAC,IAAI,CAAC,CAAC;IAExC,IAAI,SAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,SAAS,GAAG,IAAA,2BAAiB,EAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,4BAAe,CAAC;YAAE,MAAM,KAAK,CAAC;QACrD,OAAO,IAAA,eAAO,EAAC;YACb,IAAI,EAAE,YAAY;YAClB,OAAO,EACL,qDAAqD,uBAAa,sCAAsC;gBACxG,GAAG,KAAK,CAAC,OAAO,uFAAuF;gBACvG,uDAAuD;YACzD,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;SAClC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,IAAA,eAAO,EAAC;YACb,IAAI,EAAE,YAAY;YAClB,OAAO,EACL,uCAAuC,uBAAa,sDAAsD;gBAC1G,0GAA0G;gBAC1G,2BAA2B,uBAAa,sBAAsB;SACjE,CAAC,CAAC;IACL,CAAC;IAED,IAAI,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC;QAAE,OAAO,IAAA,cAAM,EAAO,SAAS,CAAC,CAAC;IAEtE,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,IAAI,EAAE,KAAK,CAAC;QACzD,CAAC,CAAC,0BAA0B,uBAAa,wEAAwE;YAC/G,+DAA+D,uBAAa,qBAAqB;QACnG,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,IAAA,eAAO,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,wCAAwC,QAAQ,IAAI,gBAAgB,EAAE,EAAE,CAAC,CAAC;AAC1H,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Immutable built-in assets pre-flight check (REQ-SEC-07 — task-042-immutable-builtin-assets).
|
|
3
|
+
*
|
|
4
|
+
* **The rule is REQ-SEC-07's, the layout is spec-011's.** REQ-SEC-07 states that built-in directives
|
|
5
|
+
* and built-in workflow templates cannot be removed; `spec-011-storage-layout` supplies the structure
|
|
6
|
+
* that rule is expressed over — the `directives/{built-in,custom}/` and `workflows/{built-in,custom}/`
|
|
7
|
+
* split, where `built-in/` is "reserved for the official … templates shipped by the `wingfoil` npm
|
|
8
|
+
* package" (empty today, `.gitkeep` only) and `custom/` holds every asset that exists right now. Only
|
|
9
|
+
* the removability rule comes from REQ-SEC-07; spec-011 never speaks about removal or protection.
|
|
10
|
+
*
|
|
11
|
+
* Classification is **structural** (which subdirectory holds the file), not the directive
|
|
12
|
+
* frontmatter's `kind:` field — and that is the one point spec-011 does settle: the six P3.8 template
|
|
13
|
+
* stand-ins live under `custom/` while each is "authored with `kind: custom`, `ref: [P3.8]`", so
|
|
14
|
+
* `kind:` demonstrably does not track the subdirectory and cannot be the discriminator. Location is
|
|
15
|
+
* the only discriminator spec-011 defines.
|
|
16
|
+
*
|
|
17
|
+
* **This check is an ALLOW-LIST and fails CLOSED.** It returns "removable" only for a path it
|
|
18
|
+
* positively recognises as `{directives|workflows}/custom/…`; every other shape — a built-in path, an
|
|
19
|
+
* unknown shape, an empty string, a `.`/`..` traversal, an absolute path, a doubled separator, a
|
|
20
|
+
* different pillar's tree — is refused. A deny-list on the literal segment `built-in` was the
|
|
21
|
+
* first-pass defect: `src/core/loaders.ts` builds a directive's stored path with the **platform**
|
|
22
|
+
* separator (`join('directives', relativePath)`), so on Windows a built-in arrives as
|
|
23
|
+
* `directives\built-in\testing.md`, which a forward-slash-only split classifies as *not* built-in and
|
|
24
|
+
* would therefore have allowed `directive remove` to delete. Both separators are accepted as
|
|
25
|
+
* separators here for exactly that reason.
|
|
26
|
+
*
|
|
27
|
+
* `.`/`..` segments are **refused, not resolved**: resolving them textually is not equivalent to
|
|
28
|
+
* resolving them on a filesystem (a symlinked `custom/` aliasing `built-in/` defeats any string-level
|
|
29
|
+
* normalisation), so an ambiguous shape is rejected rather than guessed at. Callers pass the
|
|
30
|
+
* `.wingfoil`-relative path of an asset they already located; that path is never ambiguous.
|
|
31
|
+
*
|
|
32
|
+
* This lives in `src/core` (not `src/directives`/`src/workflow`) because REQ-SEC-07 crosscuts both
|
|
33
|
+
* pillars identically and both the CLI and MCP surfaces must enforce it the same way (REQ-SYS-05) —
|
|
34
|
+
* the same reasoning `requireGitIdentity` (REQ-SEC-01, task-014) already established for a
|
|
35
|
+
* cross-cutting mutating-op pre-flight. Neither `directive remove` nor `workflow remove` exists yet in
|
|
36
|
+
* `CORE_MODULES` (spec-006 §3 reserves both as future `mutates: true` operations); per the same
|
|
37
|
+
* precedent (task-014, task-016), this task delivers the shared, fully-tested primitive now and defers
|
|
38
|
+
* wiring it into the actual mutating op to the dependent task (task-052-directive-remove for
|
|
39
|
+
* `directive.remove`; a future task for `workflow.remove`).
|
|
40
|
+
*
|
|
41
|
+
* **Scope:** this module delivers REQ-SEC-07's *first* clause only. The second — "removal of a
|
|
42
|
+
* still-referenced custom asset is rejected naming the referrer" — is deliberately not implemented
|
|
43
|
+
* here; per `dl-030-req-sec-07-referenced-asset-ownership` (`ready`) the directive half belongs to
|
|
44
|
+
* `task-052-directive-remove` and the workflow half (P4.9) is carried to the next `release-planning`
|
|
45
|
+
* run. `requireCustomAsset` is therefore the FIRST of two checks a remove op runs, never the only one.
|
|
46
|
+
*/
|
|
47
|
+
import { type CoreResult } from './types';
|
|
48
|
+
/** The two pillars REQ-SEC-07 protects — `built-in/` is immutable in both. */
|
|
49
|
+
export type AssetKind = 'directive' | 'workflow';
|
|
50
|
+
/**
|
|
51
|
+
* The allow-list: true only when `relativePath` is positively recognised as a removable `custom/`
|
|
52
|
+
* asset of this `kind` — e.g. `directives/custom/legacy-rule.md`, `workflows/custom/arch-review.yaml`,
|
|
53
|
+
* or a file nested deeper under `custom/`. Either separator is accepted (see the module doc-comment on
|
|
54
|
+
* `loaders.ts`'s platform `join`). A file merely *named* `built-in-notes.md` under `custom/` is
|
|
55
|
+
* removable — it is the `custom` **segment** that decides, never a substring. Everything else is
|
|
56
|
+
* false, including a `kind` mismatch: a workflow path handed to `'directive'` is not removable through
|
|
57
|
+
* the directive surface. Pure predicate — no filesystem access, no wall-clock/randomness (REQ-SYS-07).
|
|
58
|
+
*/
|
|
59
|
+
export declare function isRemovableCustomAssetPath(kind: AssetKind, relativePath: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* REQ-SEC-07 pre-flight: allow a removal only for a recognised `custom/` asset of `kind`. Mirrors
|
|
62
|
+
* `requireGitIdentity`'s shape exactly (`CoreResult<void>`, pure, safe to call before any read/write)
|
|
63
|
+
* — intended as the FIRST check `directiveRemove`/`workflowRemove` run, before any reference check,
|
|
64
|
+
* so a protected or unrecognised path is rejected on structure alone. Returns a `VALIDATION`
|
|
65
|
+
* `CoreResult.error` (a failed precondition, like the sibling pre-flights `requireGitIdentity` and
|
|
66
|
+
* `requireApprovalAuthority`; exit `1` via `exitCodeForError`) carrying REQ-SEC-07's exact fit-criterion
|
|
67
|
+
* wording for a declared built-in, or a generic `cannot remove '<path>': …` refusal — shaped after
|
|
68
|
+
* P3.3's own `cannot remove '<name>': …` message — for any other unrecognised shape; otherwise `ok`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function requireCustomAsset(kind: AssetKind, relativePath: string): CoreResult<void>;
|
|
71
|
+
//# sourceMappingURL=builtin-asset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtin-asset.d.ts","sourceRoot":"","sources":["../../src/core/builtin-asset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAE3D,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAiDjD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAEzF;AAcD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAQ1F"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isRemovableCustomAssetPath = isRemovableCustomAssetPath;
|
|
4
|
+
exports.requireCustomAsset = requireCustomAsset;
|
|
5
|
+
/**
|
|
6
|
+
* Immutable built-in assets pre-flight check (REQ-SEC-07 — task-042-immutable-builtin-assets).
|
|
7
|
+
*
|
|
8
|
+
* **The rule is REQ-SEC-07's, the layout is spec-011's.** REQ-SEC-07 states that built-in directives
|
|
9
|
+
* and built-in workflow templates cannot be removed; `spec-011-storage-layout` supplies the structure
|
|
10
|
+
* that rule is expressed over — the `directives/{built-in,custom}/` and `workflows/{built-in,custom}/`
|
|
11
|
+
* split, where `built-in/` is "reserved for the official … templates shipped by the `wingfoil` npm
|
|
12
|
+
* package" (empty today, `.gitkeep` only) and `custom/` holds every asset that exists right now. Only
|
|
13
|
+
* the removability rule comes from REQ-SEC-07; spec-011 never speaks about removal or protection.
|
|
14
|
+
*
|
|
15
|
+
* Classification is **structural** (which subdirectory holds the file), not the directive
|
|
16
|
+
* frontmatter's `kind:` field — and that is the one point spec-011 does settle: the six P3.8 template
|
|
17
|
+
* stand-ins live under `custom/` while each is "authored with `kind: custom`, `ref: [P3.8]`", so
|
|
18
|
+
* `kind:` demonstrably does not track the subdirectory and cannot be the discriminator. Location is
|
|
19
|
+
* the only discriminator spec-011 defines.
|
|
20
|
+
*
|
|
21
|
+
* **This check is an ALLOW-LIST and fails CLOSED.** It returns "removable" only for a path it
|
|
22
|
+
* positively recognises as `{directives|workflows}/custom/…`; every other shape — a built-in path, an
|
|
23
|
+
* unknown shape, an empty string, a `.`/`..` traversal, an absolute path, a doubled separator, a
|
|
24
|
+
* different pillar's tree — is refused. A deny-list on the literal segment `built-in` was the
|
|
25
|
+
* first-pass defect: `src/core/loaders.ts` builds a directive's stored path with the **platform**
|
|
26
|
+
* separator (`join('directives', relativePath)`), so on Windows a built-in arrives as
|
|
27
|
+
* `directives\built-in\testing.md`, which a forward-slash-only split classifies as *not* built-in and
|
|
28
|
+
* would therefore have allowed `directive remove` to delete. Both separators are accepted as
|
|
29
|
+
* separators here for exactly that reason.
|
|
30
|
+
*
|
|
31
|
+
* `.`/`..` segments are **refused, not resolved**: resolving them textually is not equivalent to
|
|
32
|
+
* resolving them on a filesystem (a symlinked `custom/` aliasing `built-in/` defeats any string-level
|
|
33
|
+
* normalisation), so an ambiguous shape is rejected rather than guessed at. Callers pass the
|
|
34
|
+
* `.wingfoil`-relative path of an asset they already located; that path is never ambiguous.
|
|
35
|
+
*
|
|
36
|
+
* This lives in `src/core` (not `src/directives`/`src/workflow`) because REQ-SEC-07 crosscuts both
|
|
37
|
+
* pillars identically and both the CLI and MCP surfaces must enforce it the same way (REQ-SYS-05) —
|
|
38
|
+
* the same reasoning `requireGitIdentity` (REQ-SEC-01, task-014) already established for a
|
|
39
|
+
* cross-cutting mutating-op pre-flight. Neither `directive remove` nor `workflow remove` exists yet in
|
|
40
|
+
* `CORE_MODULES` (spec-006 §3 reserves both as future `mutates: true` operations); per the same
|
|
41
|
+
* precedent (task-014, task-016), this task delivers the shared, fully-tested primitive now and defers
|
|
42
|
+
* wiring it into the actual mutating op to the dependent task (task-052-directive-remove for
|
|
43
|
+
* `directive.remove`; a future task for `workflow.remove`).
|
|
44
|
+
*
|
|
45
|
+
* **Scope:** this module delivers REQ-SEC-07's *first* clause only. The second — "removal of a
|
|
46
|
+
* still-referenced custom asset is rejected naming the referrer" — is deliberately not implemented
|
|
47
|
+
* here; per `dl-030-req-sec-07-referenced-asset-ownership` (`ready`) the directive half belongs to
|
|
48
|
+
* `task-052-directive-remove` and the workflow half (P4.9) is carried to the next `release-planning`
|
|
49
|
+
* run. `requireCustomAsset` is therefore the FIRST of two checks a remove op runs, never the only one.
|
|
50
|
+
*/
|
|
51
|
+
const types_1 = require("./types");
|
|
52
|
+
/** The `.wingfoil/` top-level directory that holds each pillar's assets (spec-011 layout). */
|
|
53
|
+
const ASSET_ROOT_SEGMENT = {
|
|
54
|
+
directive: 'directives',
|
|
55
|
+
workflow: 'workflows',
|
|
56
|
+
};
|
|
57
|
+
/** The spec-011 subdirectory holding removable assets — the ONE segment this allow-list accepts. */
|
|
58
|
+
const CUSTOM_SEGMENT = 'custom';
|
|
59
|
+
/** The spec-011 subdirectory reserved for official, npm-shipped templates. */
|
|
60
|
+
const BUILT_IN_SEGMENT = 'built-in';
|
|
61
|
+
/**
|
|
62
|
+
* Exact refusal wording required by REQ-SEC-07's fit criterion — **do not reword**: the BDD scenarios
|
|
63
|
+
* "Error - removing a built-in directive" (`p3-directives/P3.3-directive-remove.feature`) and
|
|
64
|
+
* "Error - removing a built-in workflow template" (`p4-workflow/P4.9-workflow-remove.feature`) assert
|
|
65
|
+
* these strings verbatim. Held in one named constant for the same reason `git-identity.ts` holds
|
|
66
|
+
* `IDENTITY_ERROR`: a fit-criterion string must be greppable against the `.feature` file that pins it,
|
|
67
|
+
* not reconstructed at each use site.
|
|
68
|
+
*/
|
|
69
|
+
const BUILT_IN_REMOVAL_ERROR = {
|
|
70
|
+
directive: 'built-in directives cannot be removed',
|
|
71
|
+
workflow: 'built-in workflows cannot be removed',
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Split a stored asset path into segments on **either** separator, without collapsing runs — so a
|
|
75
|
+
* doubled separator yields an empty segment and an absolute path yields an empty leading segment,
|
|
76
|
+
* both of which the shape check below then rejects.
|
|
77
|
+
*/
|
|
78
|
+
function segmentsOf(relativePath) {
|
|
79
|
+
return relativePath.split(/[\\/]/);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* True when `relativePath` is exactly the shape `{kind's root}/{subdirectory}/…/<file>`: at least
|
|
83
|
+
* three segments, no empty/`.`/`..` segment, the expected pillar root first and `subdirectory`
|
|
84
|
+
* second. Case-sensitive on purpose — a differently-cased segment is not recognised, and therefore
|
|
85
|
+
* (for the `custom` allow-list) refused rather than trusted on a case-insensitive filesystem.
|
|
86
|
+
*/
|
|
87
|
+
function isPillarAssetPath(kind, relativePath, subdirectory) {
|
|
88
|
+
const segments = segmentsOf(relativePath);
|
|
89
|
+
if (segments.length < 3)
|
|
90
|
+
return false;
|
|
91
|
+
if (!segments.every((segment) => segment !== '' && segment !== '.' && segment !== '..'))
|
|
92
|
+
return false;
|
|
93
|
+
return segments[0] === ASSET_ROOT_SEGMENT[kind] && segments[1] === subdirectory;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The allow-list: true only when `relativePath` is positively recognised as a removable `custom/`
|
|
97
|
+
* asset of this `kind` — e.g. `directives/custom/legacy-rule.md`, `workflows/custom/arch-review.yaml`,
|
|
98
|
+
* or a file nested deeper under `custom/`. Either separator is accepted (see the module doc-comment on
|
|
99
|
+
* `loaders.ts`'s platform `join`). A file merely *named* `built-in-notes.md` under `custom/` is
|
|
100
|
+
* removable — it is the `custom` **segment** that decides, never a substring. Everything else is
|
|
101
|
+
* false, including a `kind` mismatch: a workflow path handed to `'directive'` is not removable through
|
|
102
|
+
* the directive surface. Pure predicate — no filesystem access, no wall-clock/randomness (REQ-SYS-07).
|
|
103
|
+
*/
|
|
104
|
+
function isRemovableCustomAssetPath(kind, relativePath) {
|
|
105
|
+
return isPillarAssetPath(kind, relativePath, CUSTOM_SEGMENT);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* True when `relativePath` positively declares itself a `built-in/` asset of this `kind`. Used
|
|
109
|
+
* **only** to choose the refusal wording, never to decide whether to refuse — that decision belongs
|
|
110
|
+
* entirely to {@link isRemovableCustomAssetPath}, so this predicate cannot reintroduce a fail-open
|
|
111
|
+
* path. A shape that is neither clearly `custom/` nor clearly `built-in/` (a traversal, an odd
|
|
112
|
+
* casing) is still refused; it just gets the generic wording instead of REQ-SEC-07's, because
|
|
113
|
+
* asserting "this is a built-in" about a path we did not recognise would be a claim we cannot make.
|
|
114
|
+
*/
|
|
115
|
+
function declaresBuiltInPath(kind, relativePath) {
|
|
116
|
+
return isPillarAssetPath(kind, relativePath, BUILT_IN_SEGMENT);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* REQ-SEC-07 pre-flight: allow a removal only for a recognised `custom/` asset of `kind`. Mirrors
|
|
120
|
+
* `requireGitIdentity`'s shape exactly (`CoreResult<void>`, pure, safe to call before any read/write)
|
|
121
|
+
* — intended as the FIRST check `directiveRemove`/`workflowRemove` run, before any reference check,
|
|
122
|
+
* so a protected or unrecognised path is rejected on structure alone. Returns a `VALIDATION`
|
|
123
|
+
* `CoreResult.error` (a failed precondition, like the sibling pre-flights `requireGitIdentity` and
|
|
124
|
+
* `requireApprovalAuthority`; exit `1` via `exitCodeForError`) carrying REQ-SEC-07's exact fit-criterion
|
|
125
|
+
* wording for a declared built-in, or a generic `cannot remove '<path>': …` refusal — shaped after
|
|
126
|
+
* P3.3's own `cannot remove '<name>': …` message — for any other unrecognised shape; otherwise `ok`.
|
|
127
|
+
*/
|
|
128
|
+
function requireCustomAsset(kind, relativePath) {
|
|
129
|
+
if (isRemovableCustomAssetPath(kind, relativePath)) {
|
|
130
|
+
return (0, types_1.coreOk)(undefined);
|
|
131
|
+
}
|
|
132
|
+
const message = declaresBuiltInPath(kind, relativePath)
|
|
133
|
+
? BUILT_IN_REMOVAL_ERROR[kind]
|
|
134
|
+
: `cannot remove '${relativePath}': not a ${kind} under '${ASSET_ROOT_SEGMENT[kind]}/${CUSTOM_SEGMENT}/'`;
|
|
135
|
+
return (0, types_1.coreErr)({ code: 'VALIDATION', message });
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=builtin-asset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtin-asset.js","sourceRoot":"","sources":["../../src/core/builtin-asset.ts"],"names":[],"mappings":";;AA2GA,gEAEC;AAwBD,gDAQC;AA7ID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,mCAA2D;AAK3D,8FAA8F;AAC9F,MAAM,kBAAkB,GAA8B;IACpD,SAAS,EAAE,YAAY;IACvB,QAAQ,EAAE,WAAW;CACtB,CAAC;AAEF,oGAAoG;AACpG,MAAM,cAAc,GAAG,QAAQ,CAAC;AAEhC,8EAA8E;AAC9E,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAEpC;;;;;;;GAOG;AACH,MAAM,sBAAsB,GAA8B;IACxD,SAAS,EAAE,uCAAuC;IAClD,QAAQ,EAAE,sCAAsC;CACjD,CAAC;AAEF;;;;GAIG;AACH,SAAS,UAAU,CAAC,YAAoB;IACtC,OAAO,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,IAAe,EAAE,YAAoB,EAAE,YAAoB;IACpF,MAAM,QAAQ,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IAC1C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACtG,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,kBAAkB,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,YAAY,CAAC;AAClF,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,0BAA0B,CAAC,IAAe,EAAE,YAAoB;IAC9E,OAAO,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,mBAAmB,CAAC,IAAe,EAAE,YAAoB;IAChE,OAAO,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,kBAAkB,CAAC,IAAe,EAAE,YAAoB;IACtE,IAAI,0BAA0B,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC;QACnD,OAAO,IAAA,cAAM,EAAO,SAAS,CAAC,CAAC;IACjC,CAAC;IACD,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,EAAE,YAAY,CAAC;QACrD,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC;QAC9B,CAAC,CAAC,kBAAkB,YAAY,YAAY,IAAI,WAAW,kBAAkB,CAAC,IAAI,CAAC,IAAI,cAAc,IAAI,CAAC;IAC5G,OAAO,IAAA,eAAO,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type BuiltinTemplateKind, type BuiltinTemplateSource } from '../storage';
|
|
2
|
+
export type { BuiltinTemplateKind, BuiltinTemplateSource } from '../storage';
|
|
3
|
+
/**
|
|
4
|
+
* The first built-in template source {@link verifyBuiltinTemplates} found invalid: its `name`/`kind`
|
|
5
|
+
* (identifying which source failed) plus the exact, already-formatted abort `message` — either the
|
|
6
|
+
* P3.8 directive wording or the P4.17 workflow wording, per REQ-SEC-10's fit criterion.
|
|
7
|
+
*/
|
|
8
|
+
export interface BuiltinIntegrityFailure {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly kind: BuiltinTemplateKind;
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Schema-check every `sources` entry, in list order (REQ-SYS-07: deterministic, no unordered
|
|
15
|
+
* iteration), and return the FIRST one that fails — or `null` when every source is valid (including
|
|
16
|
+
* the trivial, always-passing case of an empty list, which is what `src/storage/templates.ts`'s
|
|
17
|
+
* `builtinTemplateSources` derives from the P1.1 minimal skeleton, whose built-in directory holds only
|
|
18
|
+
* a `.gitkeep`; `templateScaffold` yields the six P3.8 built-in directives since
|
|
19
|
+
* `task-057-builtin-directive-templates`).
|
|
20
|
+
*
|
|
21
|
+
* FAILS CLOSED on an unrecognized `kind`: a source whose kind has no {@link INTEGRITY_POLICY} entry is
|
|
22
|
+
* reported as a failure ({@link unknownKindMessage}) rather than skipped or thrown on — it cannot be
|
|
23
|
+
* checked, so it must not be installed.
|
|
24
|
+
*
|
|
25
|
+
* Callers — `initWingfoilProject` AND `initWingfoilStorage`, both in `src/core/init.ts`; every
|
|
26
|
+
* `initStorage` write path there is one — MUST run this before writing any file: REQ-SEC-10's fit
|
|
27
|
+
* criterion requires the abort to happen "before writing partial assets". This function itself never
|
|
28
|
+
* writes or reads from disk, so calling it before the scaffold write is sufficient to satisfy that
|
|
29
|
+
* ordering.
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyBuiltinTemplates(sources: readonly BuiltinTemplateSource[]): BuiltinIntegrityFailure | null;
|
|
32
|
+
//# sourceMappingURL=builtin-integrity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtin-integrity.d.ts","sourceRoot":"","sources":["../../src/core/builtin-integrity.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAItG,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAE7E;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAuFD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS,qBAAqB,EAAE,GACxC,uBAAuB,GAAG,IAAI,CAehC"}
|