@stravica-ai/digital-operator-pa 0.1.32 → 0.1.34
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/docs/gsc.md +47 -35
- package/dist/engineering-pa-cli/docs/secrets.md +34 -0
- package/dist/engineering-pa-cli/docs/staging-alias.md +5 -2
- package/dist/engineering-pa-cli/scripts/migrate-doc-paths.mjs +0 -0
- package/dist/engineering-pa-cli/src/gsc/cli.mjs +13 -8
- package/dist/engineering-pa-cli/src/gsc/cloudflare.mjs +88 -87
- package/dist/engineering-pa-cli/src/research/briefs/deep-verifier.md +7 -2
- package/dist/engineering-pa-cli/src/research/briefs/verifier.md +6 -2
- package/dist/engineering-pa-cli/src/research/engine/agents.mjs +3 -0
- package/dist/engineering-pa-cli/src/research/engine/deep.mjs +5 -50
- package/dist/engineering-pa-cli/src/research/engine/finalise.mjs +80 -4
- package/dist/engineering-pa-cli/src/research/engine/verify.mjs +7 -0
- package/dist/engineering-pa-cli/src/research/workflows/research-deep.js +93 -53
- package/dist/engineering-pa-cli/src/research/workflows/research-normal.js +137 -5
- package/dist/engineering-pa-cli/src/research/wrappers/normal.mjs +49 -3
- package/dist/engineering-pa-cli/src/secrets/cli.mjs +96 -3
- package/dist/engineering-pa-cli/src/secrets/commands/get.mjs +40 -9
- package/dist/engineering-pa-cli/src/secrets/vault.mjs +2 -1
- package/dist/engineering-pa-cli/src/staging-alias/sites.mjs +4 -2
- 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/scheduler/install.mjs +110 -4
- package/dist/engineering-pa-library-cli/bin/library +0 -0
- package/dist/engineering-pa-library-cli/package.json +1 -1
- package/dist/engineering-pa-library-cli/src/config.mjs +26 -4
- package/dist/engineering-pa-library-cli/src/verbs/status.mjs +40 -2
- package/dist/engineering-pa-library-tunnel/bin/library-tunnel +0 -0
- 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
|
|
@@ -52,34 +52,34 @@ Two npm packages (declared in `@stravica-ai/digital-operator-pa`):
|
|
|
52
52
|
single-file vault. Store the key in the pot the host is already
|
|
53
53
|
configured to read; no `pa gsc`-specific configuration is needed.
|
|
54
54
|
|
|
55
|
-
6. **Store Cloudflare credentials** (also one-time).
|
|
56
|
-
|
|
55
|
+
6. **Store Cloudflare credentials** (also one-time). Each CF account is
|
|
56
|
+
addressed by a suffixed vault-key pair. `--account <name>` is required
|
|
57
|
+
on every invocation — the unsuffixed bare pair that used to be a
|
|
58
|
+
single-account default was dropped in the 2026-09-01 rename
|
|
59
|
+
(`w-2026-08-31-dave-010`).
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
pnpm exec secrets set CLOUDFLARE_PAGES_API_KEY <token>
|
|
61
|
-
pnpm exec secrets set CLOUDFLARE_PAGES_ACCOUNT_ID <account-id>
|
|
62
|
-
```
|
|
61
|
+
Pick the surface segment that matches how the token is actually
|
|
62
|
+
scoped in Cloudflare:
|
|
63
63
|
|
|
64
|
-
**Multi-account setup** — multiple CF accounts, each addressed by name:
|
|
65
64
|
```bash
|
|
66
|
-
# Stravica account
|
|
67
|
-
pnpm exec secrets set
|
|
68
|
-
pnpm exec secrets set
|
|
65
|
+
# Stravica account (Workers Scripts + D1) → --account stravica
|
|
66
|
+
pnpm exec secrets set CLOUDFLARE_WORKERS_API_TOKEN_STRAVICA <token>
|
|
67
|
+
pnpm exec secrets set CLOUDFLARE_WORKERS_ACCOUNT_ID_STRAVICA <account-id>
|
|
69
68
|
|
|
70
|
-
# Clive account
|
|
71
|
-
pnpm exec secrets set
|
|
69
|
+
# Clive account (Pages + zone purge) → --account clive
|
|
70
|
+
pnpm exec secrets set CLOUDFLARE_PAGES_API_TOKEN_CLIVE <token>
|
|
72
71
|
pnpm exec secrets set CLOUDFLARE_PAGES_ACCOUNT_ID_CLIVE <account-id>
|
|
73
72
|
```
|
|
74
73
|
|
|
75
|
-
The CF API token needs
|
|
76
|
-
|
|
77
|
-
tokens also work.
|
|
74
|
+
The CF API token needs the permissions its surface implies (for DNS
|
|
75
|
+
planting: `Zone:DNS:Edit` and `Zone:Zone:Read` on the target zones;
|
|
76
|
+
account-level "Write to all resources" tokens also work).
|
|
78
77
|
|
|
79
|
-
For back-compat, the
|
|
80
|
-
`
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
For back-compat, the deprecated `CLOUDFLARE_PAGES_API_KEY_<NAME>` /
|
|
79
|
+
`CLOUDFLARE_PAGES_ACCOUNT_ID_<NAME>` pair is still accepted, and a
|
|
80
|
+
hit emits a stderr deprecation warning naming the current pair. Rename
|
|
81
|
+
the vault entries at your convenience — the deprecated pair will be
|
|
82
|
+
removed in a follow-up.
|
|
83
83
|
|
|
84
84
|
Same scoped-vault note as the SA key: on scoped hosts these live in
|
|
85
85
|
whichever pot `secrets get` reads (see step 5); use `secrets set --scope
|
|
@@ -134,26 +134,38 @@ pnpm exec pa gsc list-properties
|
|
|
134
134
|
|
|
135
135
|
## Multi-account selection
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
the
|
|
139
|
-
|
|
137
|
+
`--account <name>` is required on every invocation. The tool upper-cases
|
|
138
|
+
the name and tries the candidate vault-key pairs below in order — the
|
|
139
|
+
first fully-present pair wins. Storing multiple pairs for the same
|
|
140
|
+
account is a supported migration state; the tool always picks the
|
|
141
|
+
highest-priority pair that resolves.
|
|
140
142
|
|
|
141
143
|
```
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
--account
|
|
145
|
-
|
|
144
|
+
1. CLOUDFLARE_WORKERS_API_TOKEN_<NAME>
|
|
145
|
+
CLOUDFLARE_WORKERS_ACCOUNT_ID_<NAME>
|
|
146
|
+
Workers surface — Workers Scripts, D1, KV. Used by --account stravica.
|
|
147
|
+
|
|
148
|
+
2. CLOUDFLARE_PAGES_API_TOKEN_<NAME>
|
|
149
|
+
CLOUDFLARE_PAGES_ACCOUNT_ID_<NAME>
|
|
150
|
+
Pages surface — Pages + zone purge. Used by --account clive.
|
|
151
|
+
|
|
152
|
+
3. CLOUDFLARE_PAGES_API_KEY_<NAME>
|
|
153
|
+
CLOUDFLARE_PAGES_ACCOUNT_ID_<NAME>
|
|
154
|
+
Deprecated pre-2026-09-01 naming. Accepted with a stderr warning;
|
|
155
|
+
will be removed in a follow-up. Rename the vault entries at your
|
|
156
|
+
convenience.
|
|
146
157
|
```
|
|
147
158
|
|
|
148
|
-
If `--account` is passed but the
|
|
149
|
-
fails fast with a message
|
|
150
|
-
|
|
151
|
-
the safety the flag exists
|
|
159
|
+
If `--account` is passed but none of the candidate pairs are fully set,
|
|
160
|
+
the command fails fast with a message naming the primary (WORKERS) pair
|
|
161
|
+
you're expected to store. It never silently falls back to any pair not
|
|
162
|
+
addressed by the suffix — that would defeat the safety the flag exists
|
|
163
|
+
to provide.
|
|
152
164
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
165
|
+
The unsuffixed bare pair (`CLOUDFLARE_PAGES_API_KEY` /
|
|
166
|
+
`CLOUDFLARE_PAGES_ACCOUNT_ID`) and the very-legacy
|
|
167
|
+
`CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` pair are no longer
|
|
168
|
+
read — `--account <name>` is mandatory.
|
|
157
169
|
|
|
158
170
|
The account name accepts letters and digits only — anything else is a
|
|
159
171
|
usage error.
|
|
@@ -85,6 +85,40 @@ node core/tools/secrets/bin/secrets set API_KEY "sk-abc123" --no-canonical
|
|
|
85
85
|
node core/tools/secrets/bin/secrets verify --values --against /path/to/other/vault/scope.enc.json
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
### Full-scope reference form (get/set/delete/find)
|
|
89
|
+
|
|
90
|
+
Barry ruling 2026-07-15 (`w-2026-07-15-023`): every secret is referred
|
|
91
|
+
to by its full scope path at every non-interactive reference site. The
|
|
92
|
+
scope is named at the call site so provenance is visible and same-name
|
|
93
|
+
keys in different scopes cannot silently collide (a real problem today,
|
|
94
|
+
`secrets find CLOUDFLARE_WORKERS_ACCOUNT_ID_STRAVICA` returns matches
|
|
95
|
+
in both `hq-estate` and `wsd`).
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
secrets get hq-estate/CLERK_AUTH_KEY
|
|
99
|
+
secrets set clients-clive/BUNNY_API_KEY_CLIVE -
|
|
100
|
+
secrets delete seo-ops/OPENAI_API_KEY
|
|
101
|
+
secrets find hq-estate/GITHUB_PAT_MAX_READ
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Each is equivalent to the corresponding `--scope <name> <verb> KEY`
|
|
105
|
+
form. The two may be combined when they agree; a mismatch is a hard
|
|
106
|
+
error (exit 1). `--file` is not combinable with the reference form (use
|
|
107
|
+
one or the other). Bare-key calls still work and remain the default for
|
|
108
|
+
interactive use, but emit a one-line deprecation notice on stderr
|
|
109
|
+
naming the preferred `scope/KEY` form for the target vault.
|
|
110
|
+
|
|
111
|
+
### Presence check (`get KEY --exists`)
|
|
112
|
+
|
|
113
|
+
`secrets get KEY --exists` (alias `--dry-run`) probes for the key
|
|
114
|
+
without ever decrypting. Prints `present` and exits 0 when the key
|
|
115
|
+
resolves in the target vault; prints `absent` and exits 1 when it does
|
|
116
|
+
not. Combines with `--scope`, `--file`, and the `scope/KEY` reference
|
|
117
|
+
form. Use it as the migration proof primitive when moving a reference
|
|
118
|
+
site to the full-scope form: run `secrets get scope/KEY --exists` and
|
|
119
|
+
compare against the pre-migration equivalent to prove identity, without
|
|
120
|
+
ever handling the secret value itself.
|
|
121
|
+
|
|
88
122
|
### Canonical-first writes
|
|
89
123
|
|
|
90
124
|
Canonical-first dual-write is the DEFAULT for scoped `set` since 0.1.20 —
|
|
@@ -98,8 +98,11 @@ Edit `src/staging-alias/sites.mjs`. Add one entry:
|
|
|
98
98
|
},
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
…and store the vault keys
|
|
102
|
-
`
|
|
101
|
+
…and store the vault keys under the surface segment that matches how
|
|
102
|
+
the token is scoped: `CLOUDFLARE_WORKERS_API_TOKEN_NEWSITE` +
|
|
103
|
+
`CLOUDFLARE_WORKERS_ACCOUNT_ID_NEWSITE` (Workers surface), or
|
|
104
|
+
`CLOUDFLARE_PAGES_API_TOKEN_NEWSITE` + `CLOUDFLARE_PAGES_ACCOUNT_ID_NEWSITE`
|
|
105
|
+
(Pages surface). See `packages/cli/docs/gsc.md` for the candidate order.
|
|
103
106
|
|
|
104
107
|
## Exit codes
|
|
105
108
|
|
|
File without changes
|
|
@@ -53,14 +53,19 @@ Auth (one-time setup; see docs/gsc.md):
|
|
|
53
53
|
GSC_SERVICE_ACCOUNT_KEY (entire JSON contents as the value).
|
|
54
54
|
- SA email added as a user on each GSC property via the GSC dashboard.
|
|
55
55
|
|
|
56
|
-
Cloudflare DNS planting reads two vault keys.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
Cloudflare DNS planting reads two vault keys. --account <name> is
|
|
57
|
+
required (letters/digits only). The suffix is upper-cased and each
|
|
58
|
+
candidate pair below is tried in order — first fully-present pair wins.
|
|
59
|
+
|
|
60
|
+
1. CLOUDFLARE_WORKERS_API_TOKEN_<NAME> +
|
|
61
|
+
CLOUDFLARE_WORKERS_ACCOUNT_ID_<NAME>
|
|
62
|
+
(Workers surface — e.g. --account stravica)
|
|
63
|
+
2. CLOUDFLARE_PAGES_API_TOKEN_<NAME> +
|
|
64
|
+
CLOUDFLARE_PAGES_ACCOUNT_ID_<NAME>
|
|
65
|
+
(Pages surface — e.g. --account clive)
|
|
66
|
+
3. CLOUDFLARE_PAGES_API_KEY_<NAME> +
|
|
67
|
+
CLOUDFLARE_PAGES_ACCOUNT_ID_<NAME>
|
|
68
|
+
(deprecated pre-2026-09-01 naming; hit emits a stderr warning)
|
|
64
69
|
`;
|
|
65
70
|
|
|
66
71
|
export async function run(argv, overrides = {}) {
|
|
@@ -5,22 +5,29 @@
|
|
|
5
5
|
// 2. find an existing TXT record by name (idempotency)
|
|
6
6
|
// 3. create or update a TXT record
|
|
7
7
|
//
|
|
8
|
-
// Credentials are read from the SOPS vault. The vault holds
|
|
9
|
-
//
|
|
8
|
+
// Credentials are read from the SOPS vault. The vault holds account-suffixed
|
|
9
|
+
// pairs — the caller picks the suffix via the `account` option (lower-cased
|
|
10
|
+
// externally; matched case-insensitively to the upper-case suffix).
|
|
10
11
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// CLOUDFLARE_PAGES_ACCOUNT_ID_<SUFFIX>
|
|
14
|
-
// The caller picks the suffix via the `account` option (lower-cased
|
|
15
|
-
// externally; matched case-insensitively to the upper-case suffix).
|
|
12
|
+
// Vault-key shapes tried in order per account (2026-09-01 rename,
|
|
13
|
+
// w-2026-08-31-dave-010; see vault-key-rename-plan-2026-08-31.md):
|
|
16
14
|
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
15
|
+
// 1. Workers surface (Workers Scripts / D1 / KV) — e.g. Stravica:
|
|
16
|
+
// CLOUDFLARE_WORKERS_API_TOKEN_<SUFFIX>
|
|
17
|
+
// CLOUDFLARE_WORKERS_ACCOUNT_ID_<SUFFIX>
|
|
20
18
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
19
|
+
// 2. Pages surface (Pages + zone purge) — e.g. Clive:
|
|
20
|
+
// CLOUDFLARE_PAGES_API_TOKEN_<SUFFIX>
|
|
21
|
+
// CLOUDFLARE_PAGES_ACCOUNT_ID_<SUFFIX>
|
|
22
|
+
//
|
|
23
|
+
// 3. Deprecated (pre-2026-09-01) — accepted with a stderr warning during
|
|
24
|
+
// the transition window; will be removed in a follow-up:
|
|
25
|
+
// CLOUDFLARE_PAGES_API_KEY_<SUFFIX>
|
|
26
|
+
// CLOUDFLARE_PAGES_ACCOUNT_ID_<SUFFIX>
|
|
27
|
+
//
|
|
28
|
+
// The unsuffixed bare pair (CLOUDFLARE_PAGES_API_KEY / CLOUDFLARE_PAGES_
|
|
29
|
+
// ACCOUNT_ID) that used to be accepted as a single-account default was
|
|
30
|
+
// removed in this same rename — `--account <name>` is now required.
|
|
24
31
|
//
|
|
25
32
|
// `fetchImpl` and `now` are injectable for tests.
|
|
26
33
|
|
|
@@ -28,18 +35,23 @@ import { resolveCommandPaths, checkVault, secretsFileExists, sopsDecrypt, VaultE
|
|
|
28
35
|
|
|
29
36
|
const CF_API = 'https://api.cloudflare.com/client/v4';
|
|
30
37
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
// Ordered candidate templates. First fully-present pair wins per account.
|
|
39
|
+
// The `deprecated` flag drives the stderr warning + return-value flag.
|
|
40
|
+
const CANDIDATE_TEMPLATES = Object.freeze([
|
|
41
|
+
Object.freeze({
|
|
42
|
+
tokenTemplate: 'CLOUDFLARE_WORKERS_API_TOKEN_<S>',
|
|
43
|
+
accountIdTemplate: 'CLOUDFLARE_WORKERS_ACCOUNT_ID_<S>',
|
|
44
|
+
}),
|
|
45
|
+
Object.freeze({
|
|
46
|
+
tokenTemplate: 'CLOUDFLARE_PAGES_API_TOKEN_<S>',
|
|
47
|
+
accountIdTemplate: 'CLOUDFLARE_PAGES_ACCOUNT_ID_<S>',
|
|
48
|
+
}),
|
|
49
|
+
Object.freeze({
|
|
50
|
+
tokenTemplate: 'CLOUDFLARE_PAGES_API_KEY_<S>',
|
|
51
|
+
accountIdTemplate: 'CLOUDFLARE_PAGES_ACCOUNT_ID_<S>',
|
|
52
|
+
deprecated: true,
|
|
53
|
+
}),
|
|
54
|
+
]);
|
|
43
55
|
|
|
44
56
|
/**
|
|
45
57
|
* Normalise an `--account` value to the upper-case suffix used in the
|
|
@@ -65,28 +77,32 @@ export function accountSuffix(account) {
|
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
/**
|
|
68
|
-
* Build the vault
|
|
69
|
-
*
|
|
70
|
-
*
|
|
80
|
+
* Build the ordered list of vault-key candidate pairs a given `account`
|
|
81
|
+
* selection resolves to. Callers try candidates in order — first fully-
|
|
82
|
+
* present pair wins.
|
|
71
83
|
*
|
|
72
|
-
* @param {string}
|
|
73
|
-
*
|
|
84
|
+
* @param {string} account Required — the unsuffixed bare pair was dropped
|
|
85
|
+
* in the 2026-09-01 rename (w-2026-08-31-dave-010).
|
|
86
|
+
* @returns {{ candidates: Array<{tokenKey: string, accountIdKey: string, deprecated?: boolean}> }}
|
|
74
87
|
*/
|
|
75
88
|
export function resolveVaultKeys(account) {
|
|
76
|
-
if (account) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
if (!account) {
|
|
90
|
+
throw new VaultError(
|
|
91
|
+
'--account <name> is required (e.g. --account stravica). ' +
|
|
92
|
+
'The unsuffixed vault-key fallback was removed in the 2026-09-01 rename.',
|
|
93
|
+
1,
|
|
94
|
+
);
|
|
82
95
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
const s = accountSuffix(account);
|
|
97
|
+
const candidates = CANDIDATE_TEMPLATES.map((tpl) => {
|
|
98
|
+
const entry = {
|
|
99
|
+
tokenKey: tpl.tokenTemplate.replace('<S>', s),
|
|
100
|
+
accountIdKey: tpl.accountIdTemplate.replace('<S>', s),
|
|
101
|
+
};
|
|
102
|
+
if (tpl.deprecated) entry.deprecated = true;
|
|
103
|
+
return entry;
|
|
104
|
+
});
|
|
105
|
+
return { candidates };
|
|
90
106
|
}
|
|
91
107
|
|
|
92
108
|
/**
|
|
@@ -95,19 +111,18 @@ export function resolveVaultKeys(account) {
|
|
|
95
111
|
*
|
|
96
112
|
* Resolution order:
|
|
97
113
|
* 1. Test overrides (`cloudflareToken` + `cloudflareAccountId`).
|
|
98
|
-
* 2.
|
|
99
|
-
*
|
|
114
|
+
* 2. `--account` is required. Try each candidate pair in order; first
|
|
115
|
+
* fully-present pair wins. A hit on a deprecated pair emits a
|
|
116
|
+
* stderr warning (unless `warn` is overridden for tests).
|
|
100
117
|
*
|
|
101
118
|
* @param {object} overrides
|
|
102
119
|
* @param {object} [overrides.paths]
|
|
103
120
|
* @param {string} [overrides.cloudflareToken]
|
|
104
121
|
* @param {string} [overrides.cloudflareAccountId]
|
|
105
|
-
* @param {string} [overrides.account] Account selector (e.g. "stravica").
|
|
122
|
+
* @param {string} [overrides.account] Account selector (e.g. "stravica"). Required unless raw creds supplied.
|
|
106
123
|
* @param {object} [overrides.vaultData] Pre-decrypted vault map (test seam).
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* fallback paths without touching the filesystem.
|
|
110
|
-
* @returns {Promise<{token: string, accountId: string, account: string|null, vaultKeys: {tokenKey: string, accountIdKey: string}}>}
|
|
124
|
+
* @param {Function} [overrides.warn] Test seam for the deprecation warning (defaults to writing to stderr).
|
|
125
|
+
* @returns {Promise<{token: string, accountId: string, account: string|null, vaultKeys: {tokenKey: string, accountIdKey: string, deprecated?: boolean}}>}
|
|
111
126
|
*/
|
|
112
127
|
export async function loadCloudflareCreds(overrides = {}) {
|
|
113
128
|
if (overrides.cloudflareToken && overrides.cloudflareAccountId) {
|
|
@@ -118,6 +133,9 @@ export async function loadCloudflareCreds(overrides = {}) {
|
|
|
118
133
|
vaultKeys: null,
|
|
119
134
|
};
|
|
120
135
|
}
|
|
136
|
+
|
|
137
|
+
const plan = resolveVaultKeys(overrides.account);
|
|
138
|
+
|
|
121
139
|
let data;
|
|
122
140
|
if (overrides.vaultData) {
|
|
123
141
|
data = overrides.vaultData;
|
|
@@ -143,50 +161,33 @@ export async function loadCloudflareCreds(overrides = {}) {
|
|
|
143
161
|
data = await sopsDecrypt(paths, paths.secretsFile);
|
|
144
162
|
}
|
|
145
163
|
|
|
146
|
-
const
|
|
164
|
+
const warn = overrides.warn || ((msg) => process.stderr.write(`${msg}\n`));
|
|
147
165
|
|
|
148
|
-
|
|
149
|
-
if (overrides.account) {
|
|
150
|
-
const token = data[plan.tokenKey];
|
|
151
|
-
const accountId = data[plan.accountIdKey];
|
|
152
|
-
if (!token || !accountId) {
|
|
153
|
-
const missing = [];
|
|
154
|
-
if (!token) missing.push(plan.tokenKey);
|
|
155
|
-
if (!accountId) missing.push(plan.accountIdKey);
|
|
156
|
-
throw new VaultError(
|
|
157
|
-
`--account "${overrides.account}" expects vault keys [${missing.join(', ')}] but they are not set. ` +
|
|
158
|
-
`Store them with:\n secrets set ${plan.tokenKey} <token>\n secrets set ${plan.accountIdKey} <id>`,
|
|
159
|
-
1,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
token,
|
|
164
|
-
accountId,
|
|
165
|
-
account: overrides.account,
|
|
166
|
-
vaultKeys: { tokenKey: plan.tokenKey, accountIdKey: plan.accountIdKey },
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// No --account: try unsuffixed pair first, then legacy bare pair.
|
|
171
|
-
const candidates = [
|
|
172
|
-
{ tokenKey: plan.tokenKey, accountIdKey: plan.accountIdKey },
|
|
173
|
-
...(plan.fallbacks || []),
|
|
174
|
-
];
|
|
175
|
-
for (const cand of candidates) {
|
|
166
|
+
for (const cand of plan.candidates) {
|
|
176
167
|
const token = data[cand.tokenKey];
|
|
177
168
|
const accountId = data[cand.accountIdKey];
|
|
178
169
|
if (token && accountId) {
|
|
179
|
-
|
|
170
|
+
if (cand.deprecated) {
|
|
171
|
+
warn(
|
|
172
|
+
`warning: Cloudflare vault keys [${cand.tokenKey}, ${cand.accountIdKey}] are deprecated ` +
|
|
173
|
+
`(pre-2026-09-01 naming). Rename to the current pair — see ` +
|
|
174
|
+
`packages/cli/docs/gsc.md for the current key shapes.`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
token,
|
|
179
|
+
accountId,
|
|
180
|
+
account: overrides.account,
|
|
181
|
+
vaultKeys: { ...cand },
|
|
182
|
+
};
|
|
180
183
|
}
|
|
181
184
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// a thing we advertise.
|
|
185
|
+
|
|
186
|
+
const primary = plan.candidates[0];
|
|
185
187
|
throw new VaultError(
|
|
186
|
-
|
|
187
|
-
`
|
|
188
|
-
` secrets set ${
|
|
189
|
-
` secrets set ${VAULT_KEYS.accountIdUnsuffixed} <id>`,
|
|
188
|
+
`--account "${overrides.account}" expects vault keys [${primary.tokenKey}, ${primary.accountIdKey}] ` +
|
|
189
|
+
`(or one of the fallback pairs listed in packages/cli/docs/gsc.md) but none are set. ` +
|
|
190
|
+
`Store them with:\n secrets set ${primary.tokenKey} <token>\n secrets set ${primary.accountIdKey} <id>`,
|
|
190
191
|
1,
|
|
191
192
|
);
|
|
192
193
|
}
|
|
@@ -57,8 +57,13 @@ Return one of three outcomes: `confirmed`, `refuted`, `unverified-infra`.
|
|
|
57
57
|
## 4. Tools & sources
|
|
58
58
|
|
|
59
59
|
- `WebSearch` + `WebFetch` to seek contradiction, rival explanations, and counter-
|
|
60
|
-
examples. Keep it lean —
|
|
61
|
-
|
|
60
|
+
examples. Keep it lean — you are testing one synthesis, not rebuilding the sub-
|
|
61
|
+
question.
|
|
62
|
+
- **Hard tool-call cap: {{verifyToolCallCap}} calls total** (searches + fetches
|
|
63
|
+
combined). This is a real budget, not a suggestion: workflow WebSearch calls draw
|
|
64
|
+
from the PARENT session's shared budget, and a fan-out of verifiers can drain it.
|
|
65
|
+
Reach the cap without a decisive check → vote per the adversarial default (§3, §8),
|
|
66
|
+
noting in `note` that the cap was reached.
|
|
62
67
|
|
|
63
68
|
## 5. Boundaries
|
|
64
69
|
|
|
@@ -47,8 +47,12 @@ Return one of three outcomes: `confirmed`, `refuted`, `unverified-infra`.
|
|
|
47
47
|
## 4. Tools & sources
|
|
48
48
|
|
|
49
49
|
- `WebSearch` + `WebFetch` to seek contradiction and corroboration. Keep it lean —
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
you are testing one claim, not rebuilding the sub-question.
|
|
51
|
+
- **Hard tool-call cap: {{verifyToolCallCap}} calls total** (searches + fetches
|
|
52
|
+
combined). This is a real budget, not a suggestion: workflow WebSearch calls draw
|
|
53
|
+
from the PARENT session's shared budget, and a fan-out of verifiers can drain it.
|
|
54
|
+
Reach the cap without a decisive check → vote per the adversarial default (§3,
|
|
55
|
+
§8), noting in `note` that the cap was reached.
|
|
52
56
|
|
|
53
57
|
## 5. Boundaries
|
|
54
58
|
|
|
@@ -121,6 +121,9 @@ export function verifyAgent(ctx, claim, lens, model = 'sonnet', effort = 'high',
|
|
|
121
121
|
return agent(brief(ctx, role, {
|
|
122
122
|
sessionId: ctx.sessionId, claimText: claim.text, quote: claim.quote, sourceUrl: claim.url || claim.sourceId,
|
|
123
123
|
subQuestionText: claim.sqText, lens,
|
|
124
|
+
// Verifier tool-call cap: shared WebSearch budget with the parent session, so keep
|
|
125
|
+
// per-verifier calls tight (w-2026-09-01-dave-010). Defaults to 2; deep can lift.
|
|
126
|
+
verifyToolCallCap: ctx.verifyToolCallCap ?? 2,
|
|
124
127
|
}), { model, effort, phase: 'Verify', label: `verify:${claim.claimId}:${lens}`, schema: VERIFY_SCHEMA });
|
|
125
128
|
}
|
|
126
129
|
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
// stop, and testOverrides so a mechanical smoke run is self-identifying.
|
|
16
16
|
import { flattenClaims, rankAndCapPerSq } from './rank.mjs';
|
|
17
17
|
import { adjudicate } from './verify.mjs';
|
|
18
|
-
import { verifyAgent
|
|
18
|
+
import { verifyAgent } from './agents.mjs';
|
|
19
19
|
import { runDeepenRound, deepenStopReason } from './deepen.mjs';
|
|
20
|
-
import { buildSourcesJson, buildGateRecords, bucketsFor } from './finalise.mjs';
|
|
20
|
+
import { buildSourcesJson, buildGateRecords, bucketsFor, synthOrDegrade } from './finalise.mjs';
|
|
21
21
|
|
|
22
22
|
// Guaranteed floor of top claims kept per sub-question when the claim budget is capped
|
|
23
23
|
// (deep-mode fix for SQ starvation). 1 = every planned sub-question keeps its single best
|
|
@@ -316,60 +316,15 @@ export async function recordRunStart(ctx) {
|
|
|
316
316
|
// claim, but a searcher/extractor that found nothing still leaves an empty SQ). Such an
|
|
317
317
|
// SQ gets a DEGRADED finding: schema-valid, confidence 'low', zero claims, and a summary
|
|
318
318
|
// explaining the absence. This closes the deep-judge's "only N of M sub-questions
|
|
319
|
-
// answered" gap and keeps the finding count == the plan's sub-question count.
|
|
319
|
+
// answered" gap and keeps the finding count == the plan's sub-question count. The
|
|
320
|
+
// synthOrDegrade/degradedFinding/degradedSynthAgent primitives now live in finalise.mjs
|
|
321
|
+
// (shared with normal — w-2026-09-01-dave-010 fix).
|
|
320
322
|
export async function runDeepSynth(ctx, subQuestions, retained, verdictByClaimId) {
|
|
321
323
|
return (await parallel(
|
|
322
324
|
subQuestions.map((sq) => () => synthOrDegrade(ctx, sq, bucketsFor(retained, verdictByClaimId, sq.id))),
|
|
323
325
|
)).filter(Boolean);
|
|
324
326
|
}
|
|
325
327
|
|
|
326
|
-
// Synthesize one sub-question, degrading to a zero-claim finding when there is nothing to
|
|
327
|
-
// synthesize or the synthesizer fails to write a schema-valid finding — either way the SQ
|
|
328
|
-
// keeps its file, so the invariant holds.
|
|
329
|
-
async function synthOrDegrade(ctx, sq, buckets) {
|
|
330
|
-
const hasClaims = buckets.verified.length || buckets.supporting.length || buckets.refuted.length;
|
|
331
|
-
if (hasClaims) {
|
|
332
|
-
const receipt = await synthAgent(ctx, sq, buckets);
|
|
333
|
-
if (receipt && receipt.written === true) return receipt;
|
|
334
|
-
log(`synth: ${sq.id} did not write a valid finding — writing a degraded finding to preserve the finding-per-SQ invariant`);
|
|
335
|
-
return degradedSynthAgent(ctx, sq, 'the synthesizer did not produce a schema-valid finding');
|
|
336
|
-
}
|
|
337
|
-
log(`synth: ${sq.id} had no retained claims — writing a degraded (zero-claim) finding to preserve the finding-per-SQ invariant`);
|
|
338
|
-
return degradedSynthAgent(ctx, sq, 'no claims survived ranking/verification for this sub-question');
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Pure: the exact degraded finding for a sub-question (fits the EXISTING finding schema —
|
|
342
|
-
// no store change). Empty quotes/sources/votes, confidence 'low', a self-describing
|
|
343
|
-
// summary, unsourced:false.
|
|
344
|
-
export function degradedFinding(sq, reason) {
|
|
345
|
-
return {
|
|
346
|
-
subQuestionId: sq.id,
|
|
347
|
-
summary: `No verifiable findings were produced for this sub-question (${sq.text}). Reason: ${reason}. `
|
|
348
|
-
+ `This degraded finding is written to preserve the one-finding-per-sub-question invariant; treat the `
|
|
349
|
-
+ `sub-question as unanswered and re-run research for it if a complete answer is required.`,
|
|
350
|
-
quotes: [],
|
|
351
|
-
sources: [],
|
|
352
|
-
confidence: 'low',
|
|
353
|
-
verificationVotes: [],
|
|
354
|
-
unsourced: false,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Have an agent write the degraded finding verbatim and round-trip it. Returns a synth
|
|
359
|
-
// receipt (claimCount 0) so the caller's aggregation (unsourced ratio, run stats) is
|
|
360
|
-
// unaffected. No brief needed — this is a mechanical verbatim write.
|
|
361
|
-
async function degradedSynthAgent(ctx, sq, reason) {
|
|
362
|
-
const finding = degradedFinding(sq, reason);
|
|
363
|
-
const path = `${ctx.sessionDir}/findings/${sq.id}.json`;
|
|
364
|
-
return await agent([
|
|
365
|
-
`You are writing a **degraded finding** for deep-research session \`${ctx.sessionId}\`, sub-question \`${sq.id}\`.`,
|
|
366
|
-
`Write this EXACT JSON (verbatim, no edits) to \`${path}\`:`,
|
|
367
|
-
'```json', JSON.stringify(finding, null, 2), '```',
|
|
368
|
-
`Then round-trip: run \`${ctx.cli} show ${ctx.sessionId} --json\` and confirm exit 0 (it validates findings).`,
|
|
369
|
-
`Return { subQuestionId: "${sq.id}", written: true, path: "${path}", confidence: "low", claimCount: 0,`,
|
|
370
|
-
`centralConfirmed: 0, centralRefuted: 0, unsourcedCount: 0 } once the round-trip is green.`,
|
|
371
|
-
].join('\n'), { model: ctx.modelOverride || 'haiku', effort: 'low', phase: 'Synthesize', label: `synth-degraded:${sq.id}`, schema: SYNTH_SCHEMA });
|
|
372
|
-
}
|
|
373
328
|
|
|
374
329
|
// ── judge phase (advisory — spec §2.3, arch §4.2 phase 7) ──────────────────────
|
|
375
330
|
export const JUDGE_RECEIPT_SCHEMA = {
|