@stravica-ai/digital-operator-pa 0.1.26 → 0.1.27
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/dist/engineering-pa-cli/bin/estate-sync +0 -0
- package/dist/engineering-pa-cli/bin/launch +0 -0
- package/dist/engineering-pa-cli/bin/pa +0 -0
- package/dist/engineering-pa-cli/bin/pa-coordinator-hook +0 -0
- package/dist/engineering-pa-cli/bin/research +0 -0
- package/dist/engineering-pa-cli/bin/scheduler +0 -0
- package/dist/engineering-pa-cli/bin/secrets +0 -0
- package/dist/engineering-pa-cli/bin/smoke +0 -0
- package/dist/engineering-pa-cli/bin/sync +0 -0
- package/dist/engineering-pa-cli/scripts/migrate-doc-paths.mjs +0 -0
- package/dist/engineering-pa-cli/src/knowledge/context.mjs +2 -2
- package/dist/engineering-pa-cli/src/pa/session.mjs +8 -7
- package/dist/engineering-pa-core/bin/desk +0 -0
- package/dist/engineering-pa-core/bin/distill +0 -0
- package/dist/engineering-pa-core/bin/memory-sync +0 -0
- package/dist/engineering-pa-core/bin/slice +0 -0
- package/dist/engineering-pa-core/src/library-config.mjs +154 -2
- package/dist/engineering-pa-migrate-inspect/bin/migrate-inspect +0 -0
- package/dist/engineering-pa-scaffold/bin/pa-init +0 -0
- package/dist/engineering-pa-workspace/src/ws.mjs +0 -0
- package/package.json +5 -6
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
// §5.1 rules runtime unreachability non-fatal at boot; probing here
|
|
30
30
|
// would drag the boot doctrine sideways.
|
|
31
31
|
|
|
32
|
-
import { LibraryConfigError, getLibraryVariant,
|
|
32
|
+
import { LibraryConfigError, getLibraryVariant, resolveLibraryConfigWithRefs } from '../../../engineering-pa-core/src/library-config.mjs';
|
|
33
33
|
import { readPreferences } from '../../../engineering-pa-core/src/preferences.mjs';
|
|
34
34
|
|
|
35
35
|
import { getRepoRoot } from '../pa/repo-root.mjs';
|
|
@@ -243,7 +243,7 @@ export async function runContext(argv, seam = {}) {
|
|
|
243
243
|
|
|
244
244
|
let config;
|
|
245
245
|
try {
|
|
246
|
-
config =
|
|
246
|
+
config = resolveLibraryConfigWithRefs(prefs, { repoRoot });
|
|
247
247
|
} catch (err) {
|
|
248
248
|
if (err instanceof LibraryConfigError) {
|
|
249
249
|
stderr.write(`[pa knowledge context] ${err.message}\n`);
|
|
@@ -25,7 +25,7 @@ import { mergePreferences } from '../../../engineering-pa-core/src/preferences.m
|
|
|
25
25
|
import {
|
|
26
26
|
LibraryConfigError,
|
|
27
27
|
getLibraryVariant,
|
|
28
|
-
|
|
28
|
+
resolveLibraryConfigWithRefs,
|
|
29
29
|
} from '../../../engineering-pa-core/src/library-config.mjs';
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -178,16 +178,17 @@ export async function buildSessionPlan(repoRoot, seam = {}) {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
// Item 14: resolve the `library.*` block and select the CLAUDE.md
|
|
181
|
-
// rule-variant token. `
|
|
181
|
+
// rule-variant token. `resolveLibraryConfigWithRefs` throws
|
|
182
182
|
// LibraryConfigError on mode=remote misconfiguration (spec §5.1 —
|
|
183
183
|
// refuse to boot the library subsystem, no silent fallback to
|
|
184
|
-
// SIMPLE)
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
184
|
+
// SIMPLE), and additionally resolves a `library.remoteKey` carrying
|
|
185
|
+
// the full-scope vault reference form (`<scope>/<KEY_NAME>`) into
|
|
186
|
+
// its underlying literal via the `secrets` CLI. Item 15 consumes
|
|
187
|
+
// `libraryVariant` when it renders the effective CLAUDE.md and when
|
|
188
|
+
// `pa knowledge context` prints the resolved wording.
|
|
188
189
|
let libraryConfig;
|
|
189
190
|
try {
|
|
190
|
-
libraryConfig =
|
|
191
|
+
libraryConfig = resolveLibraryConfigWithRefs(merged, { repoRoot });
|
|
191
192
|
} catch (err) {
|
|
192
193
|
if (err instanceof LibraryConfigError) {
|
|
193
194
|
throw new SessionError(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -16,10 +16,31 @@
|
|
|
16
16
|
// and will land in item 15; the wiring here is stable so item 15 slots
|
|
17
17
|
// content into the existing shape without changing this file.
|
|
18
18
|
//
|
|
19
|
-
//
|
|
20
|
-
// preferences object. Callers pair it with `readPreferences` /
|
|
19
|
+
// `resolveLibraryConfig` is DELIBERATELY pure — it consumes an already-
|
|
20
|
+
// merged preferences object. Callers pair it with `readPreferences` /
|
|
21
21
|
// `readPreferencesSync` from ./preferences.mjs so the overlay-aware
|
|
22
22
|
// path stays a single implementation.
|
|
23
|
+
//
|
|
24
|
+
// `resolveRemoteKeyRef` sits alongside it as an OPT-IN vault-fetching
|
|
25
|
+
// resolver: `library.remoteKey` values that look like the full-scope
|
|
26
|
+
// reference form (`<scope>/<KEY_NAME>` per Baz's w-2026-07-15-023
|
|
27
|
+
// ruling) resolve to their vaulted literal via the `secrets` CLI so
|
|
28
|
+
// canonical stravica-operator-barry never has to carry a literal
|
|
29
|
+
// subkey in a git-tracked file. Literal values pass through untouched.
|
|
30
|
+
// The wrapper `resolveLibraryConfigWithRefs(preferences, {repoRoot})`
|
|
31
|
+
// composes the two so callers get one call: read → ref-resolve →
|
|
32
|
+
// validate, refuse-boot on any failure.
|
|
33
|
+
//
|
|
34
|
+
// Written into the platform in w-2026-08-21-005 after the 2026-08-21T11:05Z
|
|
35
|
+
// library-sync-cycle wipe: the canonical file had no library block, a
|
|
36
|
+
// materialisation tick clobbered HQ's local plaintext block, and the
|
|
37
|
+
// options collapsed to (a) put a literal secret in canonical (rejected
|
|
38
|
+
// by policy — git history is forever), (b) hold sync-cycle indefinitely,
|
|
39
|
+
// or (c) ship this resolver. The estate picked (c).
|
|
40
|
+
|
|
41
|
+
import { readFileSync } from 'node:fs';
|
|
42
|
+
import { join } from 'node:path';
|
|
43
|
+
import { execFileSync } from 'node:child_process';
|
|
23
44
|
|
|
24
45
|
/**
|
|
25
46
|
* Default when `library.mode` is absent from preferences.
|
|
@@ -120,3 +141,134 @@ export function getLibraryVariant(config) {
|
|
|
120
141
|
const mode = config?.mode ?? DEFAULT_LIBRARY_MODE;
|
|
121
142
|
return mode === 'remote' ? 'remote' : 'simple';
|
|
122
143
|
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Full-scope vault reference form for `library.remoteKey`, per Baz's
|
|
147
|
+
* ruling on w-2026-07-15-023: `<scope>/<KEY_NAME>` (e.g.
|
|
148
|
+
* `hq-estate/REMOTE_LIBRARY_SUBKEY_DAVE`). Distinct from the literal-
|
|
149
|
+
* subkey shape (`<subkeyId>:<secret>`) so the shape validator downstream
|
|
150
|
+
* still refuses garbage. Any value that neither matches this ref shape
|
|
151
|
+
* nor the literal shape is passed through and refused by the shape
|
|
152
|
+
* validator.
|
|
153
|
+
*/
|
|
154
|
+
export const REMOTE_KEY_REF_RE = /^([a-z0-9-]+)\/([A-Z][A-Z0-9_]*)$/;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolve a `library.remoteKey` value that carries the full-scope vault
|
|
158
|
+
* reference form (`<scope>/<KEY_NAME>`) into the underlying literal
|
|
159
|
+
* `<subkeyId>:<secret>` by calling the estate `secrets` CLI. Literal
|
|
160
|
+
* values (containing a colon) and anything else pass through untouched;
|
|
161
|
+
* the shape validator in `resolveLibraryConfig` is authoritative on shape.
|
|
162
|
+
*
|
|
163
|
+
* Canonical stravica-operator-barry cannot carry the literal subkey (a
|
|
164
|
+
* sync-cycle materialisation would round-trip a plaintext secret through
|
|
165
|
+
* a git-tracked file). The reference lives in canonical; the vault holds
|
|
166
|
+
* the value; this function is the bridge. See w-2026-08-21-005 for the
|
|
167
|
+
* incident that made this the durable pattern.
|
|
168
|
+
*
|
|
169
|
+
* @param {string} refOrLiteral the raw remoteKey value from preferences
|
|
170
|
+
* @param {object} opts
|
|
171
|
+
* @param {string} opts.repoRoot repo root; used to locate the secrets bin
|
|
172
|
+
* @param {(args:{scope:string,key:string,repoRoot:string})=>string} [opts.secretsGet]
|
|
173
|
+
* injectable resolver (tests)
|
|
174
|
+
* @returns {string} the resolved literal (never printed)
|
|
175
|
+
*/
|
|
176
|
+
export function resolveRemoteKeyRef(refOrLiteral, { repoRoot, secretsGet } = {}) {
|
|
177
|
+
if (typeof refOrLiteral !== 'string') return refOrLiteral;
|
|
178
|
+
const m = REMOTE_KEY_REF_RE.exec(refOrLiteral);
|
|
179
|
+
if (!m) return refOrLiteral;
|
|
180
|
+
const [, scope, key] = m;
|
|
181
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0) {
|
|
182
|
+
throw new LibraryConfigError(
|
|
183
|
+
`library.remoteKey vault reference "${refOrLiteral}" cannot be resolved: ` +
|
|
184
|
+
`no repoRoot supplied. Callers must pass repoRoot when preferences carry a scope/KEY ref.`,
|
|
185
|
+
'remote-key-ref-unresolved',
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
const fetcher = secretsGet || defaultSecretsGet;
|
|
189
|
+
let resolved;
|
|
190
|
+
try {
|
|
191
|
+
resolved = fetcher({ scope, key, repoRoot });
|
|
192
|
+
} catch (err) {
|
|
193
|
+
throw new LibraryConfigError(
|
|
194
|
+
`library.remoteKey vault reference "${refOrLiteral}" failed to resolve ` +
|
|
195
|
+
`(scope=${scope} key=${key}): ${err && err.message ? err.message : err}. ` +
|
|
196
|
+
`Fix: pnpm exec secrets set ${key} --scope ${scope} <value>`,
|
|
197
|
+
'remote-key-ref-unresolved',
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (typeof resolved !== 'string' || resolved.length === 0) {
|
|
201
|
+
throw new LibraryConfigError(
|
|
202
|
+
`library.remoteKey vault reference "${refOrLiteral}" resolved to empty value ` +
|
|
203
|
+
`(scope=${scope} key=${key}). ` +
|
|
204
|
+
`Fix: pnpm exec secrets set ${key} --scope ${scope} <value>`,
|
|
205
|
+
'remote-key-ref-unresolved',
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
// secrets get can leave a trailing newline depending on subshell; strip
|
|
209
|
+
// trailing whitespace once at the boundary so the shape validator sees
|
|
210
|
+
// the clean payload.
|
|
211
|
+
return resolved.replace(/\s+$/, '');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Read + ref-resolve + validate in one call. Wraps `resolveLibraryConfig`
|
|
216
|
+
* with `resolveRemoteKeyRef` so callers get: (1) full-scope vault refs
|
|
217
|
+
* automatically resolved via the injected `secretsGet` (or the built-in
|
|
218
|
+
* `secrets` CLI); (2) the same shape/refuse-boot guarantees on the
|
|
219
|
+
* resolved literal. Preserves the pure `resolveLibraryConfig` for
|
|
220
|
+
* caller sites that never see refs.
|
|
221
|
+
*
|
|
222
|
+
* @param {object|null} preferences merged preferences (base + overlay)
|
|
223
|
+
* @param {object} opts
|
|
224
|
+
* @param {string} opts.repoRoot required when preferences may carry a
|
|
225
|
+
* scope/KEY ref; ignored otherwise
|
|
226
|
+
* @param {(args:{scope:string,key:string,repoRoot:string})=>string} [opts.secretsGet]
|
|
227
|
+
* @returns {{mode: 'simple'|'remote', remoteEndpoint?: string, remoteKey?: string}}
|
|
228
|
+
*/
|
|
229
|
+
export function resolveLibraryConfigWithRefs(preferences, opts = {}) {
|
|
230
|
+
if (
|
|
231
|
+
preferences &&
|
|
232
|
+
typeof preferences === 'object' &&
|
|
233
|
+
!Array.isArray(preferences) &&
|
|
234
|
+
preferences.library &&
|
|
235
|
+
typeof preferences.library === 'object' &&
|
|
236
|
+
!Array.isArray(preferences.library) &&
|
|
237
|
+
preferences.library.mode === 'remote' &&
|
|
238
|
+
typeof preferences.library.remoteKey === 'string'
|
|
239
|
+
) {
|
|
240
|
+
const original = preferences.library.remoteKey;
|
|
241
|
+
const resolved = resolveRemoteKeyRef(original, opts);
|
|
242
|
+
if (resolved !== original) {
|
|
243
|
+
preferences = {
|
|
244
|
+
...preferences,
|
|
245
|
+
library: { ...preferences.library, remoteKey: resolved },
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return resolveLibraryConfig(preferences);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Default vault fetcher: invokes the `secrets` bin from the installed
|
|
254
|
+
* `@stravica-ai/digital-operator-pa` package under `repoRoot`. Uses
|
|
255
|
+
* direct `node <bin>` invocation (not `pnpm exec`) to sidestep the
|
|
256
|
+
* pnpm-11 stdout footgun that has bitten sam-tasks and others. Value
|
|
257
|
+
* is never logged.
|
|
258
|
+
*/
|
|
259
|
+
function defaultSecretsGet({ scope, key, repoRoot }) {
|
|
260
|
+
const secretsBin = join(
|
|
261
|
+
repoRoot,
|
|
262
|
+
'node_modules',
|
|
263
|
+
'@stravica-ai',
|
|
264
|
+
'digital-operator-pa',
|
|
265
|
+
'bin',
|
|
266
|
+
'secrets',
|
|
267
|
+
);
|
|
268
|
+
const out = execFileSync('node', [secretsBin, 'get', key, '--scope', scope], {
|
|
269
|
+
cwd: repoRoot,
|
|
270
|
+
encoding: 'utf8',
|
|
271
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
272
|
+
});
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stravica-ai/digital-operator-pa",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Digital Operator PA
|
|
3
|
+
"version": "0.1.27",
|
|
4
|
+
"description": "Digital Operator PA — single-bundled public package surface (bins + library) for the platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -49,11 +49,10 @@
|
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=24"
|
|
51
51
|
},
|
|
52
|
+
"//": "test pins --test-isolation=none: default process-isolation mode silently undercounts (drops passed tests from totals) when a test awaits async work while stdout is patched by a capture() helper — see packages/cli/test/secrets. Counts must never lie.",
|
|
52
53
|
"scripts": {
|
|
53
54
|
"build": "node build.mjs",
|
|
54
|
-
"prepack": "node build.mjs",
|
|
55
55
|
"test": "node --test --test-isolation=none test/*.test.mjs",
|
|
56
56
|
"dry-publish": "node build.mjs && node scripts/dry-publish.mjs"
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
}
|
|
57
|
+
}
|
|
58
|
+
}
|