Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions build/check-doc-drift.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1674,6 +1674,17 @@ for (const name of skills) {
lines.forEach((l, i) => {
for (const t of helperCodeTexts(l, inFence(i))) {
if (!t.includes("capture.mjs") || !t.includes("--paginate")) continue;
// Issue #10 (rung 5 — copies held to a shape): an --out carrying the
// {page} placeholder must be QUOTED. Windows PowerShell consumes the
// braces of an unquoted value (measured 2026-09-14: argv arrives as
// `…-`), and the shipped fences are the only home of that fact.
const outVal = /--out\s+(\S+)/.exec(t)?.[1] ?? "";
if (outVal.includes("{page}") && !/^(['"]).*\1$/.test(outVal)) {
fail(
`${rel}:${i + 1}: paginate fence's --out carries {page} unquoted — Windows PowerShell consumes the braces; ` +
`quote the value ('<path>-{page}.json') (issue #10)`,
);
}
const cmd = t.split(/\s--\s/)[1] ?? "";
for (const [needle, path] of Object.entries(PAGINATE_STATED_PATHS)) {
if (new RegExp("(^|\\s)" + needle.replace(/ /g, "\\s+") + "(\\s|$)").test(cmd) && !t.includes(`--items-path ${path}`)) {
Expand Down
5 changes: 5 additions & 0 deletions build/check-stale-facts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,11 @@ const FACT_CARRIERS = [
// The read-verb allowlist is re-audited by hand at each adoption; pinning
// the audit note's version stamp forces that re-audit when the pin moves.
{ re: /read actions in the v(\d+\.\d+\.\d+) catalog/, expect: meta.cliVersion, what: "read-verb allowlist audit version" },
// The auth-death sentence the spawn-capable scripts classify a token
// death by (F-458) is the CLI's own re-login instruction, read from the
// installed package's auth module; the version stamp beside it forces
// a re-read of that file when the pin moves.
{ re: /\/\/ v(\d+\.\d+\.\d+) package's dist\/core\/auth\/index\.js/, expect: meta.cliVersion, what: "auth-death literal audit version" },
],
},
];
Expand Down
18 changes: 18 additions & 0 deletions build/test-check-doc-drift.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,24 @@ try {
res.status === 1 && /rp list.*does not state its rows path/.test(res.stderr) && /--items-path data\.data/.test(res.stderr), res.stderr);
} finally { snap.restore(); }
}
// (j) issue #10: a paginate fence whose --out carries {page} UNQUOTED must
// go red — Windows PowerShell consumes the braces, and nothing but the
// fence carries the quoting. Mutant: strip the quotes from the audit rules
// fence.
{
const auditRel = "plugins/gs-superadmin/skills/audit/SKILL.md";
const snap = snapshotFiles([at(auditRel)]);
try {
mutate(auditRel, (s) => {
const q = "--out '.gs-superadmin/tmp/audit-rules-{page}.json'";
if (!s.includes(q)) throw new Error("audit rules fence no longer carries the quoted {page} --out — fixture stale");
return s.replace(q, "--out .gs-superadmin/tmp/audit-rules-{page}.json");
});
const res = run();
check("check 14j: a paginate fence with an UNQUOTED {page} --out goes red (issue #10)",
res.status === 1 && /audit\/SKILL\.md:\d+: paginate fence's --out carries \{page\} unquoted/.test(res.stderr), res.stderr);
} finally { snap.restore(); }
}
}

// ── check 10i (F-394): the BARE placeholder spelling inside a fence ────────
Expand Down
4 changes: 3 additions & 1 deletion build/test-check-stale-facts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ function runRig(files) {
);
// All three F-280 carriers live in doc-lib since the shared-gate hoist
// (B5 W4/DS-17 + its review round).
// …plus the auth-death literal's audit stamp (F-458), same home.
writeFileSync(
join(rig, "plugins", "gs-superadmin", "scripts", "doc-lib.mjs"),
`// strict writers went 29 → 0; POST reads 59 → 0\n// read actions in the v${PIN} catalog audited\n`,
`// strict writers went 29 → 0; POST reads 59 → 0\n// read actions in the v${PIN} catalog audited\n` +
`// the sentence every auth-path throw in the\n// v${PIN} package's dist/core/auth/index.js ends with\n`,
);
// Coverage floor (F-101): the checker requires ≥40 tracked .md files with
// some nested — filler carries no version/count/flag tokens.
Expand Down
49 changes: 45 additions & 4 deletions dev/FEEDBACK.md

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions dev/VALIDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,83 @@ premise (every dataset is a dm object) does not hold on today's sandbox — reco
numbers on the bus and do not exclude. Relationship maps are unaffected (the domain is
not one of the five lanes); note in the verdict whether `deps-report` on the sandbox still
answers for one of the 69 names through data-management.

## F-456 — live deep-ingest of the connectors-chains lane through the RECORDED describe, no manual fallback (banked 2026-09-14, builder, Session B @ hb-20260914-01)

CLEARED 2026-09-14 (tester, Session B-V) @ hb-20260914-01 — PASS, with one rig deviation. Precondition held:
domains_indexed["connectors-chains"].describeCommand is `gs-admin --json cn chain --id {id}` on both tenants.
Step 1 as written selects 0 — all 4 chains are depth full on the sandbox and on prod — so --statuses documented
replaced --upgrade (Bradley, before the run); the gate decision under test is unchanged. Steps 1–2: commandSource
recorded, selected 3, documented 3, failed 0, failures [], no aborted; each of the 3 entries carries doc_path, the
doc on disk, and a fingerprint. Step 3: capture exit 0, 1447 bytes, no BOM, no --normalize, no redirect. Chain id
shape: 36-character UUID (the payload's jobExecutionSetId). Verdict on the bus: F-456 VERIFIED.

Owed by: the tester round @ hb-20260914-01 (Session B-V; branch round-b-describe-loop, PR #19).
Tenant: either; the sandbox is preferred (fewer chains). Reads only against the tenant; the
writes are to the local workspace manifest and the domain's KB folder.
Precondition: the workspace's connectors-chains domain records `describeCommand` as
`gs-admin --json cn chain --id {id}` (the lane the finding measured) — confirm with a local
read of `<slug>/_manifest.json` `domains_indexed`. If the July fallback left it recorded as
`none`, re-record the template first: `manifest.mjs upsert-batch --describe-command
"gs-admin --json cn chain --id {id}"` over a fresh list capture of that domain.

Steps (from the consumer workspace, plugin loaded from the working tree; token pre-flight per
setup Phase 1 first):
1. `node .gs-superadmin/plugin/scripts/describe-batch.mjs --manifest <slug>/_manifest.json --domain <chains-domain> --out-dir <slug>/<chains-domain> --limit 3 --upgrade`
— with NO `--command`: the recorded template must clear the gate on its own merits.
2. Read the summary: `commandSource: "recorded"`, `documented ≥ 1`, no `aborted`; one chain's
doc under `<slug>/<chains-domain>/` with `doc_path` recorded on its entry (local read).
3. The other sanctioned route, one chain through the capture helper:
`node .gs-superadmin/plugin/scripts/capture.mjs --out .gs-superadmin/tmp/chain-probe.json -- gs-admin --json cn chain --id <one id from the manifest>`
— exit 0 and a JSON file, with no `--normalize` and no bare redirect anywhere.

Pass bar: both scripts admit the lane; no manual per-asset path, no redirect-then-normalize.
A gate refusal on either ("not a describe-shaped read" / "not a capture-shaped read")
REOPENS F-456. Record the documented count and the chain id shape on the bus.

## F-458 / #13 — one batch run PAST the token half-life: summary + manifest (banked 2026-09-14, builder, Session B @ hb-20260914-01)

CLEARED 2026-09-14 (tester, Session B-V) @ hb-20260914-01 — PASS on steps 3–5 and on step 6 (run), with two rig
deviations decided by Bradley before the runs. Rig: no sandbox domain holds ~600 eligible entries, so the batch was a
TIMED start — a background shell (no harness timeout) ran describe-batch --domain report --statuses documented
--if-changed --limit 200 (recorded describe) at 23:26:06 with whoami at 1913s; it stopped at 23:28:02 with whoami
still reading valid (1797s). Step 3: aborted.reason auth, after 80, lastError carrying the CLI's re-login sentence
(live wording: "Run `gs-admin login` to re-authenticate"), documented 79, failed 0, failures []; stderr
"ABORTED after 80 entries (auth)". Step 4 (against a manifest copy taken before any live arm): 79 report entries
re-verified, 0 status changes, the in-flight 80th entry still documented at its July last_verified, 0 failed entries
tenant-wide, 0 carrying the re-login sentence. Step 5: after gs-admin login the same command resumed; the in-flight
entry documented normally (documented/full, new last_verified, doc on disk, fingerprint, no error). Step 6: the
scorecard domain holds only 4 selectable entries, so rules-engine-chains (19) took the mismatched
`re r describe --id {id}`: aborted consecutive-failures after 5, failed 5, five distinct server Request IDs, entries
6–10 untouched; restored with the recorded command (19 of 19 documented, 0 failed). Verdict on the bus: F-458 VERIFIED;
#13 confirmed live.

Owed by: the same tester round @ hb-20260914-01 (Session B-V; branch round-b-describe-loop, PR #19).
Tenant: the sandbox. Reads only against the tenant; the writes are the local manifest and docs.
Rig: the binding deadline must be the TOKEN, not the harness — run the batch from a terminal
(not the tool shell's ~2-minute timeout) on a domain with more undocumented or
`--upgrade`-eligible assets than the token's usable life covers at the observed rate (setup
Phase 1's pre-flight formula; at ~3 s/asset a fresh token's usable life covers roughly 600
describes, so `--limit 800` on the largest stub domain runs past it).

Steps:
1. `gs-admin login` fresh; note `whoami`'s remaining seconds.
2. Run describe-batch on that domain with the oversized `--limit` and let it stop on its own.
3. Read the summary: `aborted.reason: "auth"`, `aborted.lastError` carrying the CLI's
re-login sentence, `failures: []`, `failed: 0`, `documented: N`; stderr showed
`ABORTED after <N+1> entries (auth)`.
4. Read the manifest locally: every entry the run reached is `documented` or at its prior
status; the count of entries with `status === "failed"` whose `error` contains
`gs-admin login` is 0.
5. `gs-admin login`, re-invoke the same command: the run resumes; the entry that was in
flight documents normally.
6. (#13's live confirmation, optional, harmless) on a SMALL domain, run describe-batch with
`--command` naming a describe whose id space cannot match — e.g. `gs-admin --json re r
describe --id {id}` over the scorecard domain, `--limit 10`: the run must stop after
exactly 5 spawns with `aborted.reason: "consecutive-failures"`, `after: 5`, five entries
marked `failed`; then restore them (`manifest.mjs mark --status stale` on each key, or
re-run the domain with its recorded command).

Pass bar: steps 3–5 as stated (step 6 as stated if run). Any entry marked `failed` whose
recorded error is the re-login sentence REOPENS F-458; a walk of the asset list past five
consecutive identical failures REOPENS #13.
2 changes: 1 addition & 1 deletion plugins/gs-superadmin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gs-superadmin",
"version": "0.38.0",
"version": "0.39.0",
"description": "Persistent Gainsight Admin CLI workspace — bootstrap, index, and operate on a Gainsight tenant via gs-admin.",
"author": {
"name": "BradleyDB"
Expand Down
30 changes: 30 additions & 0 deletions plugins/gs-superadmin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@ marketplace doesn't pin versions — users get main — so entries describe what
user who updates, not internal refactors. Entries before 0.8.0 were reconstructed from git
history when this file was introduced.

## 0.39.0 — 2026-09-14

The describe loop stops itself, and says why. What a user gets by updating: a dead token
or a wrong `describeCommand` no longer walks the whole asset list — `describe-batch.mjs`
aborts a domain after five consecutive failed marks (issue #13) and aborts on the first
auth failure with nothing marked (F-458), reporting either as one new summary field,
`aborted: { reason: "consecutive-failures" | "auth", after, lastError }` (absent on a run
that did not stop early; `budgetExhausted`, `moreRemaining` and `failures` are unchanged).
A run that ended because the token died therefore never records that death as an asset's
`failed` state: on one live run seventeen healthy assets carried it with the auth error as
their reason; the entry in flight now keeps its status and is simply re-offered after
`gs-admin login` (real describe failures earlier in the same run still stand).

- The read-only gate in `describe-batch.mjs` and `capture.mjs` admits a per-item describe
on its catalog `actionKey` as well as on the path's trailing word (F-456): `cn chain`
(`describe-job-chain`) and `re r execution` (`describe-execution`) are now describable
and capturable through both scripts, so the connectors-chains lane needs no manual
fallback. A committed sweep over the shipped catalog names any future refusal at
adoption time.
- Every `capture.mjs --paginate` fence in the skills quotes its `--out` value: Windows
PowerShell consumed the unquoted `{page}` placeholder and the command failed (issue #10).
The helper's refusal for a missing placeholder now names that cause.
- Operating model, Known CLI issue KI-017: the libuv-abort exit code is named as the raw Windows
status 0xC0000409 (3221226505) beside the codes Git Bash (127) and PowerShell (255) display —
a caller reading the raw status from a spawn saw a number the note did not mention.
- Setup Phase 5: the stop rule names the within-run limit and both `aborted` reasons; the
batch-sizing guidance names the nearer of the shell timeout and the token's usable life
as the binding deadline, and says to size against the slow end of observed rates. The
manual per-asset path carries the same auth exception.

## 0.38.0 — 2026-09-11

Setup Phase 4's index-or-exclude decision is bound to the overlap check's numbers. What a
Expand Down
Loading