session-orchestrator 5.9.0 → 5.10.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +33 -2
- package/README.md +10 -9
- package/docs/USER-GUIDE.md +2 -0
- package/docs/ci-setup.md +46 -1
- package/docs/events-schema.md +35 -5
- package/docs/owner-config-schema.md +18 -1
- package/docs/owner-privacy-patterns.md +85 -0
- package/docs/scope-collision-guard.md +12 -2
- package/hooks/enforce-commands.mjs +9 -19
- package/hooks/enforce-scope.mjs +23 -42
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/post-bash-write-verify.mjs +38 -21
- package/package.json +2 -2
- package/scripts/emit-session.mjs +15 -0
- package/scripts/lib/gates/gate-full.mjs +6 -0
- package/scripts/lib/gates/gate-helpers.mjs +2 -0
- package/scripts/lib/named-vault-resolver.mjs +1 -1
- package/scripts/lib/owner-yaml.mjs +33 -3
- package/scripts/lib/platform.mjs +70 -1
- package/scripts/lib/pre-push-remote-gate.mjs +107 -0
- package/scripts/lib/quality-gate.mjs +6 -1
- package/scripts/lib/redact-spans.mjs +8 -17
- package/scripts/lib/scope-gate.mjs +68 -5
- package/scripts/lib/validate/check-owner-leakage.mjs +198 -1015
- package/scripts/lib/vault-backfill/template.mjs +1 -1
- package/scripts/lib/vault-mirror/render-sessions.mjs +15 -3
- package/scripts/lib/vault-status/board-writer.mjs +15 -7
- package/scripts/migrate-vault-paths.mjs +57 -60
- package/scripts/release.mjs +103 -36
- package/scripts/vault-consolidate.mjs +19 -4
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +14 -3
- package/skills/session-end/metrics-collection.md +26 -2
- package/skills/wave-executor/references/wave-loop-review.md +26 -2
|
@@ -851,7 +851,12 @@ export function gateKillFields(run) {
|
|
|
851
851
|
* Count fallback is limited to pre-#954 sessions, gates outside the event
|
|
852
852
|
* wrapper, or missing wave attribution; step 7 falls back to this wave's own
|
|
853
853
|
* gate output (its header is not written yet), session end to the header. Auto-fix with a known wave
|
|
854
|
-
* uses the event; missing counts alone do not permit
|
|
854
|
+
* uses the event; missing counts alone do not permit count fallback.
|
|
855
|
+
* Diagnose own-session events without the counts filter to distinguish absent
|
|
856
|
+
* events, missing wave attribution, and fail-fast events without counts; never
|
|
857
|
+
* adopt counts from another session/wave or an unattributed row. A truncated
|
|
858
|
+
* append can be skipped by fromjson?, leaving an older matching run: that alone
|
|
859
|
+
* does not establish the current gate result.
|
|
855
860
|
*
|
|
856
861
|
* `suite_platform` comes from this wave's gate run (step 7) or the STATE.md
|
|
857
862
|
* Wave History header `— suite <passed>/<failed> on <platform>` (session end);
|
|
@@ -9,28 +9,19 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Behaviour is unchanged from the original: this module is a move, not a rewrite.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* (proven by the three cases in `tests/husky/pre-commit-owner-leakage.test.mjs`).
|
|
18
|
-
* Unlike CP11's confidential-names helpers, a redaction sink cannot degrade to
|
|
19
|
-
* inert — an absent redaction prints confidential names into a PUBLIC CI log.
|
|
20
|
-
* So: THIS module serves in-tree consumers, the inline copy serves the vendored
|
|
21
|
-
* path, and `tests/lib/redact-spans.test.mjs` pins the two byte-for-byte against
|
|
22
|
-
* each other. Edit one → that drift guard goes red until both agree again.
|
|
12
|
+
* The owner-leakage scanner remains a standalone vendoring target. Since #1530
|
|
13
|
+
* it omits offending line content entirely and redacts private filename segments,
|
|
14
|
+
* so it no longer carries a second copy of this primitive. In-tree consumers
|
|
15
|
+
* that retain surrounding text use this module; its overlap and ordering
|
|
16
|
+
* contracts are covered by tests/lib/redact-spans.test.mjs.
|
|
23
17
|
*/
|
|
24
18
|
|
|
25
19
|
/**
|
|
26
20
|
* Redact every confidential-name span from `line`, ORDER-INDEPENDENTLY (Fix 1 + Fix 2).
|
|
27
21
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* PUBLIC GitHub-Actions mirror: a confidential customer/repo name that co-occurs
|
|
32
|
-
* with a CP1–CP10 hit on the same line (e.g. a name beside an RFC1918 IP that
|
|
33
|
-
* fails CP8) would otherwise be echoed verbatim to the public CI log (Fix 1).
|
|
22
|
+
* Consumers must pass every relevant confidential pattern before emitting text.
|
|
23
|
+
* This helper matches the supplied text literally; callers handling reversible
|
|
24
|
+
* encodings must normalize first or omit the sensitive text entirely.
|
|
34
25
|
*
|
|
35
26
|
* ORDER-INDEPENDENCE (Fix 2): a naïve chain of `.replace()` calls is order-dependent
|
|
36
27
|
* — when one configured name is a PREFIX of another (`['acme','acme-corp-secret']`),
|
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
* hot-paths — all sync. Re-exported by hardening.mjs as a barrel so existing
|
|
6
6
|
* importers keep working unchanged.
|
|
7
7
|
*
|
|
8
|
-
* Layering: hook-safe
|
|
9
|
-
*
|
|
10
|
-
* this lib;
|
|
11
|
-
* invariant for all exports below — see #554 A2.
|
|
8
|
+
* Layering: hook-safe synchronous helpers; no I/O at import time. Guard
|
|
9
|
+
* context lookups read bounded filesystem state when called. Hooks (under
|
|
10
|
+
* `hooks/`) import this ESM-only lib; it MUST NOT reverse-import from hooks.
|
|
11
|
+
* Cross-cutting invariant for all exports below — see #554 A2.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
14
|
+
import { existsSync, readFileSync, realpathSync, lstatSync, openSync, fstatSync, readSync, closeSync, constants } from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
|
+
import { resolveSessionRoot, isolatedAgentCheckout, registeredWorktreeRoots } from './platform.mjs';
|
|
16
17
|
|
|
17
18
|
import { tokenizeCommand, splitChainSegments, resolveSegmentVerb } from './command-blocker.mjs';
|
|
18
19
|
|
|
@@ -106,6 +107,68 @@ export function findOwnScopeFile(projectRoot, ownIds, classify) {
|
|
|
106
107
|
return { path: null, foreignPath };
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Guard context: control state belongs to the coordinator, relative grants to
|
|
112
|
+
* the actual isolated checkout. Remote adoption requires unique OWN identity;
|
|
113
|
+
* local unknown/corrupt/foreign/missing dispositions remain unchanged.
|
|
114
|
+
* Root-only discovery deliberately excludes arbitrary worktree subdirectories;
|
|
115
|
+
* revisit when the harness supplies a coordinator-root witness (#1504).
|
|
116
|
+
* @param {{cwd: string, ownIds: Set<string>, classify: Function}} options
|
|
117
|
+
* @returns {{manifestRoot: string, writeRoot: string, path: string|null,
|
|
118
|
+
* foreignPath: string|null, isolated: boolean, error: string|null}}
|
|
119
|
+
*/
|
|
120
|
+
export function resolveScopeContext({ cwd, ownIds, classify }) {
|
|
121
|
+
let manifestRoot = resolveSessionRoot(cwd);
|
|
122
|
+
try { manifestRoot = realpathSync.native(manifestRoot); } catch { /* preserve non-existing-root fallback */ }
|
|
123
|
+
const local = findOwnScopeFile(manifestRoot, ownIds, classify);
|
|
124
|
+
const agent = isolatedAgentCheckout(cwd);
|
|
125
|
+
const context = { manifestRoot, writeRoot: agent?.writeRoot ?? manifestRoot,
|
|
126
|
+
...local, isolated: agent !== null, error: null };
|
|
127
|
+
if (!agent || local.path || ownIds.size === 0) return context;
|
|
128
|
+
const candidates = registeredWorktreeRoots(agent.commonDir);
|
|
129
|
+
if (candidates.error) return { ...context, error: candidates.error };
|
|
130
|
+
const matches = new Map();
|
|
131
|
+
for (const root of candidates.roots) {
|
|
132
|
+
if (root === agent.writeRoot || root === manifestRoot || isolatedAgentCheckout(root)) continue;
|
|
133
|
+
// Inspect precedence candidates separately. An unsafe candidate has unknown
|
|
134
|
+
// ownership: it must never turn an isolated session into a no-scope ALLOW.
|
|
135
|
+
for (const dir of SCOPE_FILE_DIRS) {
|
|
136
|
+
const file = path.join(root, dir, 'wave-scope.json');
|
|
137
|
+
let fd;
|
|
138
|
+
let raw;
|
|
139
|
+
try {
|
|
140
|
+
const before = lstatSync(file);
|
|
141
|
+
if (!before.isFile() || before.size > 1024 * 1024) return { ...context, error: 'unsafe-coordinator-scope' };
|
|
142
|
+
fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
143
|
+
const opened = fstatSync(fd);
|
|
144
|
+
if (!opened.isFile() || opened.ino !== before.ino || opened.dev !== before.dev || opened.size > 1024 * 1024) return { ...context, error: 'unsafe-coordinator-scope' };
|
|
145
|
+
const buffer = Buffer.alloc(1024 * 1024 + 1);
|
|
146
|
+
let length = 0;
|
|
147
|
+
while (length < buffer.length) {
|
|
148
|
+
const n = readSync(fd, buffer, length, buffer.length - length, null);
|
|
149
|
+
if (n === 0) break;
|
|
150
|
+
length += n;
|
|
151
|
+
}
|
|
152
|
+
if (length > 1024 * 1024) return { ...context, error: 'unsafe-coordinator-scope' };
|
|
153
|
+
raw = buffer.toString('utf8', 0, length);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (err?.code === 'ENOENT') continue;
|
|
156
|
+
return { ...context, error: 'unsafe-coordinator-scope' };
|
|
157
|
+
} finally { if (fd !== undefined) closeSync(fd); }
|
|
158
|
+
try {
|
|
159
|
+
const scope = JSON.parse(raw);
|
|
160
|
+
if (classify(scope, ownIds).verdict === 'own') {
|
|
161
|
+
matches.set(realpathSync(file), { root, path: file, foreignPath: null });
|
|
162
|
+
break; // highest-precedence positively owned manifest in this root
|
|
163
|
+
}
|
|
164
|
+
} catch { /* unbound/corrupt remote state is never authority */ }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (matches.size > 1) return { ...context, error: 'ambiguous-coordinator-scope' };
|
|
168
|
+
const match = matches.values().next().value;
|
|
169
|
+
return match ? { ...context, manifestRoot: match.root, path: match.path, foreignPath: null } : context;
|
|
170
|
+
}
|
|
171
|
+
|
|
109
172
|
/**
|
|
110
173
|
* Read the enforcement level from a scope file.
|
|
111
174
|
* Defaults to "strict" (fail-closed) on parse error or missing field.
|