@orkestrel/scaffold 0.0.53 → 0.0.55

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.
@@ -128,6 +128,7 @@ Every file in the following table is a normative extension of this root. Read ev
128
128
  | `.claude/rules/application.md` | App composition, entries, manifest safety, lifecycle, integration |
129
129
  | `.claude/rules/browser.md` | Vue/browser architecture and platform usage |
130
130
  | `.claude/rules/styles.md` | SCSS/CSS centralization, tokens, mixins, layers, naming |
131
+ | `.claude/rules/portability.md` | Host branching, paths, line endings, processes, terminals, OS claims |
131
132
  | `.claude/rules/documentation.md` | Guides, parity, roadmap, showcase, examples |
132
133
  | `.claude/rules/writing.md` | Developer prose: reports, replies, vocabulary, claims, structure |
133
134
  | `.claude/rules/quality.md` | Research, dependency reuse, hardening, completion, package inspection |
@@ -730,6 +730,12 @@ transport.
730
730
  **When a brief assigns a bench unit a path outside the obvious source tree, name the write limit
731
731
  in the brief.** If the sandbox rejects the patch, the unit stops and reports the rejection. Never
732
732
  find another write mechanism.
733
+ **A bench unit writes only under its own `--cd` root and the system temporary directory.** A
734
+ report path in the orchestrator's repository is outside both, so the write is rejected after the
735
+ work is already done and the unit improvises a fallback the Orchestrator has to go find. Name the
736
+ report path inside the unit's own tree, or tell the unit to return the report as its final
737
+ message and take it from the `--output-last-message` file. Retention still lands the report in
738
+ `.orkestrel/<package>/` — the Orchestrator copies it there, and the unit never writes it there.
733
739
  6. **Ephemeral streams, durable records.** A journal proves a bench is alive and recovers an
734
740
  interrupted session. Keep journals under `tmp/`, never commit them, and sweep them at acceptance
735
741
  after the final gate evidence is recorded. Durable retention — brief, distillate, verdict,
@@ -44,26 +44,27 @@ so network-controlled descriptions never enter agent instruction context.
44
44
  | `@orkestrel/abort` | `0.0.8` | L1 | `@orkestrel/contract` `^0.0.13` |
45
45
  | `@orkestrel/agent` | `0.0.18` | L5 | `@orkestrel/abort` `^0.0.7`, `@orkestrel/budget` `^0.0.7`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.11`, `@orkestrel/emitter` `^0.0.7`, `@orkestrel/queue` `^0.0.10`, `@orkestrel/timeout` `^0.0.7`, `@orkestrel/tool` `^0.0.11`, `@orkestrel/workflow` `^0.0.14`, `@orkestrel/workspace` `^0.0.5` |
46
46
  | `@orkestrel/brief` | `0.0.6` | L4 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/interpret` `^0.0.11`, `@orkestrel/reason` `^0.0.8` |
47
- | `@orkestrel/browser` | `0.0.12` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.7`, `@orkestrel/html` `^0.0.4`, `@orkestrel/websocket` `^0.0.9` |
47
+ | `@orkestrel/browser` | `0.0.14` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/html` `^0.0.7`, `@orkestrel/websocket` `^0.0.10` |
48
48
  | `@orkestrel/budget` | `0.0.8` | L1 | `@orkestrel/contract` `^0.0.13` |
49
- | `@orkestrel/console` | `0.0.10` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
49
+ | `@orkestrel/console` | `0.0.11` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
50
50
  | `@orkestrel/contract` | `0.0.13` | L0 | |
51
51
  | `@orkestrel/csv` | `0.0.5` | L1 | `@orkestrel/contract` `^0.0.13` |
52
52
  | `@orkestrel/database` | `0.0.12` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/indexeddb` `^0.0.9`, `@orkestrel/sqlite` `^0.0.9` |
53
53
  | `@orkestrel/emitter` | `0.0.8` | L1 | `@orkestrel/contract` `^0.0.13` |
54
54
  | `@orkestrel/form` | `0.0.3` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
55
- | `@orkestrel/guide` | `0.0.13` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/markdown` `^0.0.10` |
56
- | `@orkestrel/html` | `0.0.6` | L1 | `@orkestrel/contract` `^0.0.13` |
55
+ | `@orkestrel/guide` | `0.0.14` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/markdown` `^0.0.11` |
56
+ | `@orkestrel/html` | `0.0.7` | L1 | `@orkestrel/contract` `^0.0.13` |
57
57
  | `@orkestrel/indexeddb` | `0.0.9` | L1 | `@orkestrel/contract` `^0.0.13` |
58
58
  | `@orkestrel/interpret` | `0.0.11` | L3 | `@orkestrel/reason` `^0.0.8`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/template` `^0.0.5` |
59
- | `@orkestrel/markdown` | `0.0.11` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/html` `^0.0.6` |
60
- | `@orkestrel/mcp` | `0.0.24` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/process` `^0.0.6`, `@orkestrel/sse` `^0.0.5`, `@orkestrel/tool` `^0.0.12`, `@orkestrel/websocket` `^0.0.10` |
59
+ | `@orkestrel/lsp` | `0.0.3` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/process` `^0.0.6` |
60
+ | `@orkestrel/markdown` | `0.0.12` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/html` `^0.0.7` |
61
+ | `@orkestrel/mcp` | `0.0.25` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/process` `^0.0.6`, `@orkestrel/sse` `^0.0.5`, `@orkestrel/tool` `^0.0.12`, `@orkestrel/websocket` `^0.0.10` |
61
62
  | `@orkestrel/middleware` | `0.0.17` | L2 | `@orkestrel/abort` `^0.0.7`, `@orkestrel/budget` `^0.0.7`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/timeout` `^0.0.7` |
62
63
  | `@orkestrel/msg` | `0.0.8` | L0 | |
63
64
  | `@orkestrel/ndjson` | `0.0.8` | L1 | `@orkestrel/contract` `^0.0.13` |
64
65
  | `@orkestrel/ollama` | `0.0.12` | L6 | `@orkestrel/agent` `^0.0.18`, `@orkestrel/budget` `^0.0.8`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/ndjson` `^0.0.8`, `@orkestrel/timeout` `^0.0.8`, `@orkestrel/tool` `^0.0.12` |
65
66
  | `@orkestrel/pool` | `0.0.8` | L2 | `@orkestrel/emitter` `^0.0.7` |
66
- | `@orkestrel/probe` | `0.0.6` | L4 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/mcp` `^0.0.24`, `@orkestrel/queue` `^0.0.10`, `@orkestrel/timeout` `^0.0.8`, `@orkestrel/tool` `^0.0.12` |
67
+ | `@orkestrel/probe` | `0.0.9` | L4 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/lsp` `^0.0.3`, `@orkestrel/mcp` `^0.0.25`, `@orkestrel/queue` `^0.0.10`, `@orkestrel/timeout` `^0.0.8`, `@orkestrel/tool` `^0.0.12` |
67
68
  | `@orkestrel/process` | `0.0.6` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.7` |
68
69
  | `@orkestrel/program` | `0.0.10` | L4 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.7`, `@orkestrel/qualifier` `^0.0.11`, `@orkestrel/rater` `^0.0.11`, `@orkestrel/reason` `^0.0.7` |
69
70
  | `@orkestrel/qualifier` | `0.0.11` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.7`, `@orkestrel/reason` `^0.0.7` |
@@ -72,7 +73,7 @@ so network-controlled descriptions never enter agent instruction context.
72
73
  | `@orkestrel/reason` | `0.0.8` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
73
74
  | `@orkestrel/relation` | `0.0.10` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.12`, `@orkestrel/emitter` `^0.0.8` |
74
75
  | `@orkestrel/router` | `0.0.11` | L2 | `@orkestrel/abort` `^0.0.7`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.7` |
75
- | `@orkestrel/scaffold` | `0.0.52` | L3 | `@orkestrel/console` `^0.0.10`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/markdown` `^0.0.10`, `@orkestrel/process` `^0.0.6`, `@orkestrel/template` `^0.0.5` |
76
+ | `@orkestrel/scaffold` | `0.0.54` | L3 | `@orkestrel/console` `^0.0.10`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/markdown` `^0.0.12`, `@orkestrel/process` `^0.0.6`, `@orkestrel/template` `^0.0.5` |
76
77
  | `@orkestrel/sea` | `0.0.11` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/process` `^0.0.6` |
77
78
  | `@orkestrel/server` | `0.0.15` | L3 | `@orkestrel/abort` `^0.0.8`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/router` `^0.0.11`, `@orkestrel/timeout` `^0.0.8` |
78
79
  | `@orkestrel/sqlite` | `0.0.9` | L1 | `@orkestrel/contract` `^0.0.13` |
@@ -80,14 +81,14 @@ so network-controlled descriptions never enter agent instruction context.
80
81
  | `@orkestrel/supervisor` | `0.0.1` | L5 | `@orkestrel/contract` `^0.0.11`, `@orkestrel/database` `^0.0.9`, `@orkestrel/emitter` `^0.0.6`, `@orkestrel/workflow` `^0.0.12` |
81
82
  | `@orkestrel/table` | `0.0.3` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
82
83
  | `@orkestrel/template` | `0.0.5` | L2 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/emitter` `^0.0.8` |
83
- | `@orkestrel/terminal` | `0.0.12` | L3 | `@orkestrel/console` `^0.0.10`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.12`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/form` `^0.0.3`, `@orkestrel/sse` `^0.0.5` |
84
+ | `@orkestrel/terminal` | `0.0.13` | L3 | `@orkestrel/console` `^0.0.11`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.12`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/form` `^0.0.3`, `@orkestrel/sse` `^0.0.5` |
84
85
  | `@orkestrel/test` | `0.0.11` | L0 | |
85
86
  | `@orkestrel/timeout` | `0.0.8` | L1 | `@orkestrel/contract` `^0.0.13` |
86
87
  | `@orkestrel/tool` | `0.0.12` | L1 | `@orkestrel/contract` `^0.0.13` |
87
88
  | `@orkestrel/toolbox` | `0.0.9` | L6 | `@orkestrel/agent` `^0.0.18`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.11`, `@orkestrel/form` `^0.0.2`, `@orkestrel/relation` `^0.0.9`, `@orkestrel/server` `^0.0.14`, `@orkestrel/terminal` `^0.0.11`, `@orkestrel/tool` `^0.0.11`, `@orkestrel/workflow` `^0.0.14`, `@orkestrel/workspace` `^0.0.5` |
88
89
  | `@orkestrel/websocket` | `0.0.10` | L2 | `@orkestrel/emitter` `^0.0.8` |
89
90
  | `@orkestrel/worker` | `0.0.9` | L4 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.11`, `@orkestrel/emitter` `^0.0.7`, `@orkestrel/pool` `^0.0.8`, `@orkestrel/queue` `^0.0.10` |
90
- | `@orkestrel/workflow` | `0.0.14` | L4 | `@orkestrel/abort` `^0.0.7`, `@orkestrel/budget` `^0.0.7`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.11`, `@orkestrel/emitter` `^0.0.7`, `@orkestrel/queue` `^0.0.10`, `@orkestrel/timeout` `^0.0.7` |
91
+ | `@orkestrel/workflow` | `0.0.15` | L4 | `@orkestrel/abort` `^0.0.8`, `@orkestrel/budget` `^0.0.8`, `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.12`, `@orkestrel/emitter` `^0.0.8`, `@orkestrel/queue` `^0.0.10`, `@orkestrel/timeout` `^0.0.8` |
91
92
  | `@orkestrel/workspace` | `0.0.6` | L3 | `@orkestrel/contract` `^0.0.13`, `@orkestrel/database` `^0.0.12`, `@orkestrel/emitter` `^0.0.8` |
92
93
 
93
94
  <!-- /orkestrel:catalog -->
@@ -106,6 +106,17 @@ kind. It reads declaration syntax and file name, never meaning.
106
106
  no bare collection literal.
107
107
  - It proves that no source, test, config, or script file carries an `eslint-disable` or
108
108
  `oxlint-disable` directive.
109
+ - It proves that every `.claude/rules/*.md` file has a rule-map row in `AGENTS.md` and that every
110
+ row resolves to a file.
111
+ - It proves the host portability rules that are path- or text-shaped over the populations
112
+ `POLICY_PORTABILITY_GLOB` and `POLICY_PORTABILITY_SOURCE_GLOB` name: no path segment carries a
113
+ Windows reserved device name, a character Windows refuses, a trailing dot or space, or a sibling
114
+ differing only by case; no `package.json` script names a `.sh` file; and no source in the parsed
115
+ population trims a payload before splitting it on `'\n'`, reads `os.EOL`, or imports `EOL` from
116
+ `node:os`.
117
+ - It cannot write a filename the host refuses. A Windows host rejects `<`, folds a case collision
118
+ into one file, and turns `:` into an alternate data stream, so those boundaries are proven from a
119
+ path population rather than from written files.
109
120
  - It does not prove a collection is frozen. It reads the declaration, never the value a call
110
121
  returns, so `Object.freeze([…])` and any other call initializer are one syntax to it. The freeze
111
122
  obligation in the earlier kind-purity rules binds regardless; only the bare literal is mechanical.
@@ -0,0 +1,98 @@
1
+ ---
2
+ paths:
3
+ - 'src/**/*'
4
+ - 'app/**/*'
5
+ - 'configs/**/*'
6
+ - 'tests/**/*'
7
+ - 'scripts/**/*'
8
+ - 'guides/**/*'
9
+ - 'package.json'
10
+ - '.gitattributes'
11
+ - '.github/workflows/*'
12
+ ---
13
+
14
+ # Host portability rules
15
+
16
+ Published source runs on Windows and Linux. Write one implementation that is correct on each host,
17
+ or gate the host-specific branch on the host that needs it.
18
+
19
+ Take the test half from `.claude/rules/tests.md`: it governs a host-varying property inside a test
20
+ and the form of a conditional skip.
21
+
22
+ ## Host branching
23
+
24
+ - Branch on a capability a runtime probe answers — a returned value, a thrown code, a present
25
+ member — wherever the difference is detectable.
26
+ - Branch on `process.platform` only where the behavior is platform-defined, and name that fact in
27
+ the symbol's `@remarks`.
28
+ - Put a platform-literal table — install root, executable suffix, toolchain name — inside a
29
+ `process.platform` branch, and read it on that platform alone.
30
+ - Never flatten a correctly-gated host branch into a single path, and never relax a fail-closed
31
+ check to make one host pass.
32
+
33
+ ## Line endings
34
+
35
+ - Split arrived text on `/\r\n|\n/`. Never split on `\n` alone, and never split on a bare `\r`.
36
+ - Split first, then trim each line; trimming the whole payload leaves a `\r` on every line but the
37
+ last.
38
+ - Frame a named protocol — HTTP, LSP, RFC 6455, NDJSON — with the terminator that protocol fixes,
39
+ and leave that terminator alone.
40
+ - Emit `\n` from code this package owns. Never emit `os.EOL`.
41
+
42
+ ## Paths
43
+
44
+ - Normalize each operand of a path comparison with the same normalizer before comparing.
45
+ - Never case-fold a containment check. Fold a lookup key only by the file-name case sensitivity the
46
+ consumer declares, and fold the whole key when you fold at all.
47
+ - Compose, split, and relativize a host path with `node:path`. Never concatenate a separator
48
+ literal.
49
+ - Keep a storage key, an archive key, and a URI path slash-separated, and refuse `\` and a drive
50
+ prefix inside one.
51
+ - Refuse the Windows reserved device names and the filename characters Windows rejects, on every
52
+ host.
53
+ - Read the temporary directory from `os.tmpdir()`. Never write a `/tmp` literal in source.
54
+ - Build a `file:` URI with `pathToFileURL`. Never format one from a path string.
55
+
56
+ ## Processes and executables
57
+
58
+ - Resolve, spawn, and terminate a child through `@orkestrel/process` where the package declares it.
59
+ - Where it is not declared, spawn `process.execPath` with a JavaScript entry. Never spawn a `.bin`
60
+ shim, and never add `shell: true` to reach one.
61
+ - Take a resolver's first match only after splitting its output into lines and trimming each one.
62
+ `where` prints a match per line and can name a file that is not an executable.
63
+ - Treat `fs.constants.X_OK` as an existence check on Windows, where `accessSync` passes on a plain
64
+ file. Confirm an executable by its name and its file type instead.
65
+ - Read an `fs.constants` member before putting it in a flag word. Windows leaves `O_NOFOLLOW`
66
+ undefined, so `O_WRONLY | O_NOFOLLOW` refuses nothing there. Never polyfill the missing member:
67
+ compensate with a check the host supports, or record the limit.
68
+ - Read `PROGRAMFILES`, `LOCALAPPDATA`, and `APPDATA` inside the win32 branch and `HOME` inside the
69
+ POSIX branch. Never read one host's root on the other.
70
+ - Terminate a Windows child by its process tree. A `SIGTERM` handler never runs there, so gate every
71
+ cooperative-shutdown path on POSIX.
72
+ - Treat a permission bit as advisory on Windows: a written mode does not round-trip
73
+ (`chmodSync(dir, 0o500)` reads back `0444` there), so assert modes only where a probe reports they
74
+ round-trip.
75
+ - Retry an `EBUSY`, `ENOTEMPTY`, or `EPERM` removal inside a bounded budget, and report the failure
76
+ when the budget ends.
77
+
78
+ ## Terminals
79
+
80
+ - Gate styling and interactivity on `isTTY`, `NO_COLOR`, and `FORCE_COLOR`, never on the platform.
81
+ - Decode `\r`, `\n`, and `\r\n` from a key stream as the same key.
82
+ - Never call a Windows console-mode API. Node exposes none, so record the unreached behavior as a
83
+ limit.
84
+
85
+ ## Scripts and packaging
86
+
87
+ - Write every `package.json` script as a portable command: a Node invocation or an installed binary.
88
+ Never name a `.sh` file there.
89
+ - Keep `#!/usr/bin/env node` on an npm bin. npm writes the Windows shim from it.
90
+ - Keep an agent hook under `scripts/` in bash, and keep `.gitattributes` `eol=lf`.
91
+
92
+ ## Claims
93
+
94
+ - Never describe the suite, the host, or CI as POSIX. Name the host and the date behind a reading.
95
+ - Run the gate chain on each host a change claims, and record a reading that could not run as a
96
+ limit naming the host it needs.
97
+ - Keep a POSIX-shaped `file:///` literal in a guide only as an opaque fictional identifier. Build
98
+ any example a reader executes with `pathToFileURL`.
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
3
+ "ignorePatterns": [".orkestrel/"],
3
4
  "plugins": ["import", "typescript", "vitest"],
4
5
  "jsPlugins": [{ "name": "policy", "specifier": "./configs/policy.ts" }],
5
6
  "rules": {
@@ -9,3 +9,6 @@ demo/showcase.html
9
9
 
10
10
  # Campaign records under the orchestration folder are verbatim evidence, not formatted source.
11
11
  .orkestrel/
12
+
13
+ # Fetched-bytes mirrors keep their upstream bytes and stay out of the formatter.
14
+ tests/mirrors/
@@ -456,16 +456,18 @@ package distributes; each operation reports one baseline word per surface. A sur
456
456
  `floor` only where the package distributes a copy. The registry's organization membership ships
457
457
  nowhere, so `catalog` refuses when that read fails.
458
458
 
459
- For `new`, `repair`, `catalog`, and `overwrite`, authoritative absence never selects `floor`. A
460
- registry `404` or a packument with no admitted version stays a `FETCH` refusal, because writing a
461
- version the registry says is absent produces an uninstallable manifest. `audit` turns release
462
- absence into questions and returns its audit result. Transport faults, timeouts, rate refusals,
463
- byte-bound refusals, and integrity refusals can select the floor.
464
-
465
- The guide surface is the per-row exception to whole-surface fallback. A failed foreign guide keeps
466
- the target's existing mirror as its floor, while the other guide rows can still update. When at
467
- least one selected guide keeps its mirror, `provenance.guides` is `floor` for the result; it is
468
- `live` only when every selected guide resolved live.
459
+ For `new`, `repair`, `catalog`, and `overwrite`, authoritative absence on a version surface never
460
+ selects `floor`. A registry `404` or a packument with no admitted version stays a `FETCH` refusal,
461
+ because writing a version the registry says is absent produces an uninstallable manifest. `audit`
462
+ turns release absence into questions and returns its audit result. Transport faults, timeouts, rate
463
+ refusals, byte-bound refusals, and integrity refusals can select the floor.
464
+
465
+ The guide surface is the per-row exception to whole-surface fallback, for absence as well as for
466
+ faults. A foreign guide the host could not serve a failed read, or the `404` a published package
467
+ with a private repository answers with — keeps the target's existing mirror as its floor, while the
468
+ other guide rows can still update. When at least one selected guide keeps its mirror,
469
+ `provenance.guides` is `floor` for the result; it is `live` only when every selected guide resolved
470
+ live.
469
471
 
470
472
  A value `Host` can carry live host-owned bytes beside installed floor bytes for deferred guide and
471
473
  catalog paths. Each surface still contributes one baseline. Deferred paths are presence-only, and
@@ -1096,13 +1098,13 @@ Each verb resolves a surface's complete answer before it opens that surface's wr
1096
1098
  a partial answer never becomes a partial pin set. `overwrite` keeps the repair and removal work it
1097
1099
  committed before a later catalog refusal and records that refusal in `note`.
1098
1100
 
1099
- | Verb | Reads live | When the network forces a floor | With `--offline` |
1100
- | ----------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
1101
- | `new` | Declared versions and the vendored host | Writes the distributed version and host floors; exits `0` after creating the workspace | Reads no upstream surface, writes the same floors, and exits `0` after creating the workspace |
1102
- | `audit` | Declared versions and the vendored host | Compares through the distributed floors and exits `1` | Compares through the floors; exits `0` for an aligned target or `1` for drift |
1103
- | `repair` | Declared versions and the vendored host | Repairs from the distributed floors and exits `1`, even when the terminal audit is aligned | Repairs from the floors; the terminal audit decides exit `0` or `1` |
1104
- | `catalog` | Organization membership, its packuments, and the selected guides | Refuses a membership or version failure with `FETCH` and exit `1`; preserves each failed guide's local mirror and exits `1` | Is a usage error; exits `2` and writes nothing |
1105
- | `overwrite` | Everything `repair` and `catalog` read | Keeps completed repair and deletion work, names each floor or refused catalog step in `note`, and exits `1` | Repairs, deletes, and writes version floors; skips `catalog`, records that refusal in `note`, and exits `1` |
1101
+ | Verb | Reads live | When the network forces a floor | With `--offline` |
1102
+ | ----------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
1103
+ | `new` | Declared versions and the vendored host | Writes the distributed version and host floors; exits `0` after creating the workspace | Reads no upstream surface, writes the same floors, and exits `0` after creating the workspace |
1104
+ | `audit` | Declared versions and the vendored host | Compares through the distributed floors and exits `1` | Compares through the floors; exits `0` for an aligned target or `1` for drift |
1105
+ | `repair` | Declared versions and the vendored host | Repairs from the distributed floors and exits `1`, even when the terminal audit is aligned | Repairs from the floors; the terminal audit decides exit `0` or `1` |
1106
+ | `catalog` | Organization membership, its packuments, and the selected guides | Refuses a membership or version failure with `FETCH` and exit `1`; preserves the local mirror of each guide that failed or is absent upstream and exits `1` | Is a usage error; exits `2` and writes nothing |
1107
+ | `overwrite` | Everything `repair` and `catalog` read | Keeps completed repair and deletion work, names each floor or refused catalog step in `note`, and exits `1` | Repairs, deletes, and writes version floors; skips `catalog`, records that refusal in `note`, and exits `1` |
1106
1108
 
1107
1109
  A fleet row is compared exactly — `^0.1.0` is stale the moment the registry serves `0.1.2` — and
1108
1110
  that inequality alone raises `audit` to exit `1`. A foreign row is compared inside its declared
@@ -4,7 +4,7 @@
4
4
  "storage": "AGENTS.md",
5
5
  "destination": "AGENTS.md",
6
6
  "executable": false,
7
- "digest": "5dbb143394991882d36f90a8aabd7e6b0874863b45dde4040760fc834e9d942e"
7
+ "digest": "43ce847aa96a525919dbe948a2311214c312d58069054800577e5aff89024f08"
8
8
  },
9
9
  {
10
10
  "storage": "CLAUDE.md",
@@ -22,7 +22,7 @@
22
22
  "storage": "agents/orchestration.md",
23
23
  "destination": ".agents/orchestration.md",
24
24
  "executable": false,
25
- "digest": "3681749c3a0a0d601dacf78c7726a2018d0ea7643d96dbd77641c186ee2cd01e"
25
+ "digest": "db4414f1dc8eb2cc9e96cb920f08946d4be2507b081866839fea4112949b7505"
26
26
  },
27
27
  {
28
28
  "storage": "agents/skills/enterprise-bootstrap/SKILL.md",
@@ -310,7 +310,7 @@
310
310
  "storage": "claude/agents/orkestrel.md",
311
311
  "destination": ".claude/agents/orkestrel.md",
312
312
  "executable": false,
313
- "digest": "3d1d0b4ee196465d831178f5aa1c425325f39d4f9087654aac48b877f728af3d"
313
+ "digest": "eacfb94cd758ed202aba25ac759420223bae1e624a7baf208feaeb9c979e3c9f"
314
314
  },
315
315
  {
316
316
  "storage": "claude/agents/planner.md",
@@ -358,7 +358,7 @@
358
358
  "storage": "claude/rules/architecture.md",
359
359
  "destination": ".claude/rules/architecture.md",
360
360
  "executable": false,
361
- "digest": "94f70c20f927129cf9cb3965b3180a14e941e4f6eddf6512c4adc014e0bcbdf2"
361
+ "digest": "674bcc17f8127948e11391cbbedfd7396911d88c0e99cfefe718f718ed06a333"
362
362
  },
363
363
  {
364
364
  "storage": "claude/rules/browser.md",
@@ -384,6 +384,12 @@
384
384
  "executable": false,
385
385
  "digest": "c128dace792fcc042446f3d35dce90ef3918d44449a424dbd6dcea4dfcab24f5"
386
386
  },
387
+ {
388
+ "storage": "claude/rules/portability.md",
389
+ "destination": ".claude/rules/portability.md",
390
+ "executable": false,
391
+ "digest": "b948c92e3409efd01b45ed9e8d917f4713cda0ecd5ea343932613b2977a614ff"
392
+ },
387
393
  {
388
394
  "storage": "claude/rules/quality.md",
389
395
  "destination": ".claude/rules/quality.md",
@@ -628,13 +634,13 @@
628
634
  "storage": "dotfiles/oxlintrc.json",
629
635
  "destination": ".oxlintrc.json",
630
636
  "executable": false,
631
- "digest": "d4154c90721ff32cb1e4a709ac4672ca7bf31c0c9d757a0643397b2c8fbcff0d"
637
+ "digest": "c2d9715f5db07992601cf764532c9d9c7b11177a366d46d767ce07f50f49acad"
632
638
  },
633
639
  {
634
640
  "storage": "dotfiles/prettierignore",
635
641
  "destination": ".prettierignore",
636
642
  "executable": false,
637
- "digest": "9a2f99856efdf7a1c1cd806e40997a4621237de5a037ee60b2ac0bd23a3979cb"
643
+ "digest": "6f8bd3b8a192c974ae6332f017c2e76179d15e2598fbd624f7cd4e9e57156ff2"
638
644
  },
639
645
  {
640
646
  "storage": "guides/guide.md",
@@ -646,7 +652,7 @@
646
652
  "storage": "guides/scaffold.md",
647
653
  "destination": "guides/scaffold.md",
648
654
  "executable": false,
649
- "digest": "0a69b1cf0194078be9a54c9c51d88029d7c7d93a41bce63001e038f588f175ce"
655
+ "digest": "4b6e503d74188118ebba5ff4c9d76d9c9a933c0aec055ae600e02540e73c8c57"
650
656
  },
651
657
  {
652
658
  "storage": "scripts/codex.sh",
@@ -682,13 +688,13 @@
682
688
  "storage": "tests/policy.test.ts",
683
689
  "destination": "tests/policy.test.ts",
684
690
  "executable": false,
685
- "digest": "4642dfcfe2c2d928373ba42026323c4eba976908dd4382ee2ff1d1faf5d766a2"
691
+ "digest": "6a2be29c887e90b976273f8ef9da9ac1c17ed8c1880c133eb8a67514ddf5b5c3"
686
692
  },
687
693
  {
688
694
  "storage": "tests/setupPolicy.ts",
689
695
  "destination": "tests/setupPolicy.ts",
690
696
  "executable": false,
691
- "digest": "3714a96bda5aeb5bbdcc8223d33a7ef77562214303d6c58acfec33af182bdc45"
697
+ "digest": "e33cc2f49a5bc201e339f50c59d280d36d06a5b687bc080a00b41f09577fa7c7"
692
698
  }
693
699
  ],
694
700
  "roots": [
@@ -736,5 +742,5 @@
736
742
  ".codex/agents",
737
743
  ".cursor/rules"
738
744
  ],
739
- "digest": "938ab0fd0dc511cf6d138259d14c58de31eaec665ce65ce2ac4ef62e5f31ae32"
745
+ "digest": "900545f6b4a0078f1ab0256867453576ade0fca62344a3fd258d249cc6853823"
740
746
  }
File without changes
File without changes
File without changes
File without changes
@@ -5,7 +5,9 @@ import {
5
5
  FUNCTION_SOURCE_FILES,
6
6
  GENERIC_POLICY_SOURCES,
7
7
  inspectPolicyControl,
8
+ inspectPolicyFilenamePaths,
8
9
  inspectPolicyMirrorPaths,
10
+ inspectPolicyPortability,
9
11
  inspectPolicySources,
10
12
  inspectPolicyWorkspace,
11
13
  inspectSkillFamily,
@@ -14,7 +16,13 @@ import {
14
16
  parseSkillFrontmatter,
15
17
  POLICY_CONTROLS,
16
18
  POLICY_SUPPRESSION_DIRECTIVE,
19
+ PORTABILITY_POLICY_CONTROLS,
20
+ PORTABILITY_POLICY_EXCLUSION,
21
+ PORTABILITY_POLICY_LOCAL,
22
+ PORTABILITY_POLICY_SPLIT,
23
+ readPolicyPaths,
17
24
  readSkillFamily,
25
+ RULES_POLICY_CONTROLS,
18
26
  SKILL_POLICY_APOSTROPHE,
19
27
  SKILL_POLICY_BACKTICKED,
20
28
  SKILL_POLICY_CONTROLS,
@@ -413,8 +421,84 @@ describe('skill bridge policy', () => {
413
421
  }
414
422
  })
415
423
 
424
+ describe('rule map policy', () => {
425
+ for (const control of RULES_POLICY_CONTROLS) {
426
+ it(`${control.label} [membership: ${control.membership}]`, () => {
427
+ const violations = inspectPolicyControl(control)
428
+ expect(violations).toHaveLength(1)
429
+ expect(violations[0]?.rule).toBe(control.rule)
430
+ expect(control.message === undefined || violations[0]?.message === control.message).toBe(true)
431
+ })
432
+ }
433
+ })
434
+
435
+ describe('portability policy', () => {
436
+ for (const control of PORTABILITY_POLICY_CONTROLS) {
437
+ it(`${control.label} [membership: ${control.membership}]`, () => {
438
+ const violations = inspectPolicyControl(control)
439
+ expect(violations).toHaveLength(1)
440
+ expect(violations[0]?.rule).toBe(control.rule)
441
+ expect(control.message === undefined || violations[0]?.message === control.message).toBe(true)
442
+ })
443
+ }
444
+
445
+ for (const control of [
446
+ PORTABILITY_POLICY_EXCLUSION,
447
+ PORTABILITY_POLICY_LOCAL,
448
+ PORTABILITY_POLICY_SPLIT,
449
+ ]) {
450
+ it(`${control.label} [membership: ${control.membership}]`, () => {
451
+ expect(inspectPolicyControl(control)).toEqual([])
452
+ })
453
+ }
454
+
455
+ it('rejects a character Windows refuses inside a path segment', () => {
456
+ // A Windows host refuses to create this name, so the population itself is the control.
457
+ expect(inspectPolicyFilenamePaths(['src/worker/read<write>.ts'])).toEqual([
458
+ {
459
+ rule: 'portability',
460
+ path: 'src/worker/read<write>.ts',
461
+ message: 'path segments avoid the characters Windows refuses',
462
+ },
463
+ ])
464
+ })
465
+
466
+ it('rejects sibling paths that differ by case alone', () => {
467
+ // A Windows host folds the pair into one file, so the population itself is the control.
468
+ expect(inspectPolicyFilenamePaths(['guides/Readme.md', 'guides/readme.md'])).toEqual([
469
+ {
470
+ rule: 'portability',
471
+ path: 'guides/readme.md',
472
+ message: 'path differs from guides/Readme.md by case alone',
473
+ },
474
+ ])
475
+ })
476
+
477
+ it('accepts sibling paths that differ by more than case', () => {
478
+ expect(
479
+ inspectPolicyFilenamePaths([
480
+ 'guides/readme.md',
481
+ 'guides/readmes.md',
482
+ 'src/worker/helpers.ts',
483
+ ]),
484
+ ).toEqual([])
485
+ })
486
+ })
487
+
416
488
  describe('repository policy', () => {
417
489
  it('enforces placement and mirrors over the real workspace', () => {
418
490
  expect(inspectPolicyWorkspace(process.cwd())).toEqual([])
419
491
  })
492
+
493
+ it('reaches every branch of the workspace-authored path population', () => {
494
+ const paths = readPolicyPaths(process.cwd())
495
+ expect(paths).toContain('tests/setupPolicy.ts')
496
+ expect(paths).toContain('.claude/rules/names.md')
497
+ expect(paths).toContain('package.json')
498
+ expect(paths).toContain('.gitattributes')
499
+ })
500
+
501
+ it('keeps every workspace path, script, and source portable', () => {
502
+ expect(inspectPolicyPortability(process.cwd())).toEqual([])
503
+ })
420
504
  })