feat: componentize GStack 2 runtime and release integrity

This commit is contained in:
Sinabina
2026-07-20 14:16:23 -07:00
parent b0ea2296d1
commit f14445bb00
270 changed files with 9681 additions and 51572 deletions
+52 -23
View File
@@ -25,6 +25,7 @@ import { GSTACK2_BASE_SHA, TREE_NAMES, type DispatcherDefinition, type SourceAss
const GENERATED = '<!-- GENERATED by scripts/gstack2/generate-skill-tree.ts; do not edit. -->';
const DOCS = path.join(ROOT, 'docs', 'gstack-2');
const EVALS = path.join(ROOT, 'evals', 'parity');
const RUNTIME_SKILL_CONTRACT = { schemaVersion: 1, runtimeVersion: '2.0.0', skillApi: '2.0' } as const;
function sha256(value: string | Uint8Array): string {
return createHash('sha256').update(value).digest('hex');
@@ -128,7 +129,7 @@ function provenanceDetails(assignment: SourceAssignment, target: string): Record
invocation_conditions: dispatcherMode?.inferWhen ?? `Internal compatibility invocation /${assignment.source}.`,
modes: { public: assignment.publicMode, legacy_alias: assignment.mode },
question_sequence: contract.question_order,
follow_up_behavior: 'The complete source follow-up sequence is preserved verbatim inside new_location and hash-compared to the pinned base.',
follow_up_behavior: 'Specialist follow-up order is preserved from the pinned source. The retired 1.x shared onboarding wrapper is excluded, and carved specialist phases are package-local lazy references loaded at their original workflow point.',
smart_skip_rules: contract.smart_skips,
pushback_rules: contract.pressure,
stop_gates: contract.stop_approval_gates,
@@ -142,7 +143,7 @@ function provenanceDetails(assignment: SourceAssignment, target: string): Record
voice: contract.voice,
response_posture: 'Direct, evidence-first builder language; preserve source-specific recommendations and constructive pressure.',
new_location: target,
parity_test: `${contractPath} + scripts/gstack2/run-parity.ts normalized full-body equality`,
parity_test: `${contractPath} + scripts/gstack2/run-parity.ts canonical specialist render, excluded-wrapper, lazy-section, and pinned-source provenance checks`,
};
}
@@ -152,7 +153,7 @@ function renderModule(assignment: SourceAssignment): { content: string; renderSh
const overlays = overlaysForSource(assignment.source);
const disposition = assignment.source === 'gstack-upgrade'
? 'DUPLICATE_INFRASTRUCTURE'
: overlays.length ? 'BUG_FIX' : 'MECHANICAL_PORT';
: overlays.length ? 'BUG_FIX' : 'JUDGMENT_PRESERVING_CARVE';
const overlayText = overlays.map((overlay) => [
`<!-- GSTACK2_BUG_FIX_START pr=${overlay.pr} anchor=${overlay.anchor} -->`,
`## Upstream judgment port: PR #${overlay.pr}`,
@@ -229,10 +230,10 @@ Web context: <none, optional, local-browser, or production>
1. Infer the mode from product stage, surface, requested artifact, mutation authorization, evidence needs, and deployment state. Do not route by keyword alone.
2. Refine the public mode to the smallest applicable internal specialist set, then print the required execution header before any substantive output.
3. Read each active module in full from the path shown in the mode/alias tables. Its legacy body, behavioral contract, STOP gates, and appended upstream judgment ports are binding.
4. Read \`references/SHARED-JUDGMENT.md\` and \`references/AUTHORITY-POLICY.md\` for every invocation. Read \`references/WEB-CONTEXT.md\` before public-web or optional-runtime work.
3. Read each active module in full from the path shown in the mode/alias tables. Its specialist body, behavioral contract, STOP gates, and appended upstream judgment ports are binding. Read a lazy specialist phase in full only when the workflow reaches its package-local reference.
4. Read \`references/SHARED-JUDGMENT.md\` and \`references/AUTHORITY-POLICY.md\` for every invocation. Read \`references/RUNTIME.md\` before capability-dependent work and \`references/WEB-CONTEXT.md\` before public-web work.
5. If an old asset path is unavailable, use \`references/ASSETS.md\`. If legacy prose invokes another retired skill, resolve it through \`references/COMPATIBILITY.md\` and stay inside these six dispatchers.
6. Preserve report-only versus mutation boundaries. Commits, pushes, PRs, merges, deploys, messages, and other external mutations still require the authority stated by the active module and the user.
6. Preserve report-only versus mutation boundaries. Missing mutation authorization fails closed: do not edit merely because a specialist can fix. Commits, pushes, PRs, merges, deploys, messages, and other external mutations still require affirmative authority from the user.
7. Match the user's language. Keep code identifiers, commands, and source quotations original when translation would reduce accuracy.
8. At exit, report completed artifacts, evidence, unresolved decisions, skipped modules with reasons, and any blocked gate.
${supplemental}
@@ -257,7 +258,7 @@ ${internalRows}
## Completeness invariant
Do not work from this dispatcher summary when a module is active. Read the referenced module completely, including its provenance marker, behavioral contract, full mechanically rendered source, and bug-fix overlays.
Do not work from this dispatcher summary when a module is active. Read the referenced module completely, including its provenance marker, specialist workflow, lazy-phase directives, behavioral contract, and bug-fix overlays. The pinned 1.x shared onboarding wrapper is provenance-only and never runs during canonical execution.
`;
}
@@ -481,11 +482,37 @@ function webContextContract(): string {
'',
'Persist only the explicit choice with `gstack context select host`, `gstack context select local-browser`, or `gstack context select none`. For Context.dev, show `gstack context options`, then use `gstack context setup` and its hidden key prompt; consent and key storage belong to the runtime, never this judgment prompt. Do not infer Context choice or consent.',
'',
'Capability-dependent work performs one host-neutral runtime check. Pure judgment never requires the runtime. If the runtime is absent, offer ./setup from a trusted GStack checkout; skill placement remains npx skills add time-attack/gstack.',
'Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack` and is never inferred from runtime state.',
'',
].join('\n');
}
function runtimeContract(): string {
return `${GENERATED}
# Optional runtime capabilities
The six Agent Skills are useful without a GStack runtime. Never install, download, build, select, update, or remove runtime capabilities merely because a skill was invoked.
When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Before any network preview, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub request for signed manifest metadata and sends no repository content, private URL, file, cookie, token, or credential; then STOP. A cached already-verified manifest may preview offline, but never silently fetch.
Only after the user approves that metadata check, run the non-mutating preview from this skill root: \`node references/support/runtime-bootstrap.mjs preview --capability <name>\` (repeat \`--capability\` for additional requested capabilities). It dependency-expands, reports already verified local components, exact missing components, and their summed compressed bytes. It never downloads components or mutates runtime state. Preview consent is not install consent.
User-facing setup capabilities are exactly \`browser\`, \`design\`, \`diagram\`, \`pdf\`, and \`ios\`. \`all\` means those five and intentionally excludes visible Chromium. The internal \`browser-visible\` capability is additive and is offered only when a workflow actually reaches a headed browser, extension, or browser-handoff step. Never offer it during ordinary headless QA.
After showing the complete preview, STOP for explicit approval. Only after approval run \`node references/support/runtime-bootstrap.mjs install --capability <name> --yes\`; install must reprint the identical dependency-closed plan before downloading. Signed internal components are \`core\`, \`browser-code\` (browse code and dependencies), \`browser-headless\` (Playwright headless shell and FFmpeg), \`browser-visible\` (full Chromium), \`design\`, \`diagram\`, \`pdf\`, and \`ios\`. Logical \`browser\` expands to \`browser-code + browser-headless\`; internal \`browser-visible\` expands to \`browser-code + browser-visible\` and does not require headless. Component dependencies are \`browser-code → core\`, \`browser-headless → browser-code\`, and \`browser-visible → browser-code\`. \`diagram\` depends on logical \`browser\`; \`pdf\` depends on \`diagram\`; \`ios\` is Darwin-only. Therefore a first-time headed flow previews \`core + browser-code + browser-visible\`, while an existing verified headless runtime downloads only missing \`browser-visible\`. The manifest schema is v2 with global \`capabilityComponents\` and \`componentDependencies\`, plus \`targets[target].components[id]\` carrying signed exact-byte artifacts.
The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates manifest/runtime/skill API/platform/component metadata and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run \`./setup\` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent.
After doctor confirms readiness, canonical launcher bindings are \`GSTACK_HOME=\${GSTACK_HOME:-$HOME/.gstack}\`, \`GSTACK_BIN=$GSTACK_HOME/bin\`, \`BUN_CMD=$GSTACK_BIN/bun\`, \`B=$GSTACK_BIN/browse\`, \`D=$GSTACK_BIN/gstack-design\`, and \`P=$GSTACK_BIN/make-pdf\`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. Never download or install a second Bun for GStack helpers; the managed runtime owns its pinned Bun executable in the active slot.
Some retained helpers are shell scripts. \`gstack doctor\` verifies Bash and, on Windows, discovers Git for Windows Bash; disclose and stop at the affected helper if that prerequisite is unavailable. Python is not a global GStack prerequisite: only a specialist flow explicitly labeled as Python-dependent may request it, at the point of use and with the user's approval.
The package/runtime compatibility tuple is \`schemaVersion=1\`, \`runtimeVersion=2.0.0\`, and \`skillApi=2.0\`; the machine-readable copy is \`references/support/runtime-contract.json\`. An incompatible active runtime is unavailable, not permission to upgrade it.
The developer-only fallback is \`node references/support/runtime-bootstrap.mjs install --source <reviewed-checkout> --capability <name> --yes\`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment.
`;
}
function systemFunctionalContract(): string {
return `${GENERATED}
# System-functional QA
@@ -531,10 +558,14 @@ Do not put secrets in run IDs, effect keys, or command arguments. Existing appro
}
function writeSharedContracts(): void {
const bootstrap = fs.readFileSync(path.join(ROOT, 'runtime', 'runtime-bootstrap.mjs'));
for (const tree of TREE_NAMES) {
write(path.join(ROOT, 'skills', tree, 'references', 'SHARED-JUDGMENT.md'), sharedJudgmentContract());
write(path.join(ROOT, 'skills', tree, 'references', 'AUTHORITY-POLICY.md'), authorityPolicyContract());
write(path.join(ROOT, 'skills', tree, 'references', 'WEB-CONTEXT.md'), webContextContract());
write(path.join(ROOT, 'skills', tree, 'references', 'RUNTIME.md'), runtimeContract());
write(path.join(ROOT, 'skills', tree, 'references', 'support', 'runtime-bootstrap.mjs'), bootstrap);
writeJson(path.join(ROOT, 'skills', tree, 'references', 'support', 'runtime-contract.json'), RUNTIME_SKILL_CONTRACT);
}
write(path.join(ROOT, 'skills', 'qa', 'references', 'SYSTEM-FUNCTIONAL.md'), systemFunctionalContract());
write(path.join(ROOT, 'skills', 'ship', 'references', 'EXTERNAL-EFFECTS.md'), externalEffectsContract());
@@ -663,7 +694,7 @@ function migrationDoc(): string {
Pinned baseline: \`${GSTACK2_BASE_SHA}\`.
GStack 2 exposes exactly six public Codex skills: \`plan\`, \`design\`, \`qa\`, \`debug\`, \`review\`, and \`ship\`. The 55 legacy templates remain mechanically rendered as internal reference modules; all 16 carved section templates are inlined with the canonical Codex resolver path. Thirty-one primary modules are mandatory specialist inputs, and 24 supporting modules remain reachable through compatibility routing.
GStack 2 exposes exactly six public skills: \`plan\`, \`design\`, \`qa\`, \`debug\`, \`review\`, and \`ship\`. The specialist bodies from 55 legacy templates remain provenance-pinned internal reference modules. The retired 1.x shared onboarding wrapper is excluded from canonical execution, and all 16 carved specialist sections are package-local lazy references loaded only at their original workflow point. Thirty-one primary modules are mandatory specialist inputs, and 24 supporting modules remain reachable through compatibility routing.
The fixed public modes are: Design = \`Explore | Generate | Critique | Implement\`; QA = \`Report | Fix\`; Debug = \`Diagnose-only | Fix\`; Review = \`Normal | Security | Performance | Deep\`; Ship = \`Prepare | Land | Deploy | Monitor | Resume\`. Richer legacy modes are internal aliases only.
@@ -681,8 +712,8 @@ ${rows}
## Mechanical versus judgment changes
- \`MECHANICAL_PORT\`: canonical Codex resolver expansion, section inlining, safety prose, and path rewrites only.
- \`BUG_FIX\`: the mechanical body plus a clearly delimited judgment overlay sourced from one of the 16 upstream PRs and its regression fixture.
- \`JUDGMENT_PRESERVING_CARVE\`: pinned specialist workflow with the retired shared onboarding wrapper excluded, retired invocations resolved to six public routes, host/runtime paths normalized, and large carved phases loaded lazily from package-local pinned references.
- \`BUG_FIX\`: the canonical carved body plus a clearly delimited judgment overlay sourced from one of the 16 upstream PRs and its regression fixture.
- Asset relocation is byte-for-byte from the pinned Git blob and is indexed per tree.
`;
}
@@ -709,14 +740,14 @@ The suite verifies:
- exactly six discoverable public skills and 55 internal legacy modules;
- 55 canonical templates plus 16 carved section templates at base \`${GSTACK2_BASE_SHA}\`;
- normalized legacy-body SHA-256 equality between source rendering and generated references;
- immutable full 1.x render hashes plus canonical specialist-render equality, with the excluded onboarding wrapper and lazy section references asserted explicitly;
- preservation of nine behavioral contract dimensions per module;
- 25 structured non-keyword routing fixtures with active/skipped modules, depth, mutation, and web context;
- 16 upstream judgment-port regression fixtures and anchors;
- all linked asset copies against their pinned Git blobs;
- frontmatter and \`agents/openai.yaml\` schema for each public skill.
Golden normalization removes only generated provenance wrappers, bug-fix overlays, and irrelevant whitespace. It never removes legacy workflow prose, gates, questions, evidence requirements, artifacts, or exit behavior.
Golden normalization removes only generated provenance wrappers, bug-fix overlays, and irrelevant whitespace. Canonical rendering separately excludes the retired shared onboarding wrapper and packages large specialist phases as lazy references; parity checks both decisions explicitly without claiming full-body byte equality.
`;
}
@@ -817,9 +848,7 @@ function main(): void {
const sectionRecords = legacySections().map((section) => {
const parent = sourceRecords.find((entry) => entry.source === section.source);
if (!parent) throw new Error(`Section without parent assignment: ${section.relativePath}`);
const generated = fs.readFileSync(path.join(ROOT, String(parent.target)), 'utf8');
const portedSection = renderPortedLegacySection(section);
if (!generated.includes(portedSection.trim())) throw new Error(`Rendered section missing from ${parent.target}: ${section.relativePath}`);
const packagedTargets = sectionCopies.filter((copy) => copy.source_path === section.relativePath).map((copy) => copy.target_path);
const ownerTarget = packagedTargets.find((target) => target.startsWith(`skills/${String(parent.tree)}/`));
if (!ownerTarget) throw new Error(`Section was not packaged with its owner: ${section.relativePath}`);
@@ -831,21 +860,21 @@ function main(): void {
normalized_render_sha256: sha256(section.rendered),
ported_render_sha256: sha256(portedSection),
target: ownerTarget,
inlined_module_target: parent.target,
lazy_reference_module_target: parent.target,
packaged_targets: packagedTargets,
disposition: 'MECHANICAL_PORT',
disposition: 'JUDGMENT_PRESERVING_CARVE',
original_source_file: section.relativePath,
original_line_range: sourceLineRange(section.relativePath),
purpose: `Carved specialist section from ${section.source}, mechanically inlined into its preserved module.`,
purpose: `Carved specialist section from ${section.source}, packaged behind a lazy reference at its original workflow point.`,
invocation_conditions: `Loaded only when the parent ${section.source} workflow reaches this carved section.`,
modes: { parent: section.source },
question_sequence: 'Preserved verbatim in the inlined section.',
follow_up_behavior: 'Preserved verbatim in the inlined section.',
question_sequence: 'Preserved verbatim in the package-local lazy section.',
follow_up_behavior: 'Preserved verbatim in the package-local lazy section and loaded only when its original phase is reached.',
smart_skip_rules: 'Inherited unchanged from the parent specialist workflow.',
pushback_rules: 'Inherited unchanged from the parent specialist workflow.',
stop_gates: 'Inherited unchanged from the parent specialist workflow.',
approval_gates: 'Inherited unchanged from the parent specialist workflow.',
rubrics_and_scoring: 'Preserved verbatim in the inlined section.',
rubrics_and_scoring: 'Preserved verbatim in the package-local lazy section.',
cognitive_frameworks: sourceHeadings(section.rendered),
evidence_requirements: 'Inherited unchanged from the parent specialist workflow.',
artifacts_produced: 'Inherited unchanged from the parent specialist workflow.',
@@ -854,7 +883,7 @@ function main(): void {
voice: 'Inherited unchanged from the parent specialist workflow.',
response_posture: 'Inherited unchanged from the parent specialist workflow.',
new_location: ownerTarget,
parity_test: `scripts/gstack2/run-parity.ts exact packaged-section, inline inclusion, and Git-blob checks`,
parity_test: `scripts/gstack2/run-parity.ts exact packaged-section, lazy-reference, and Git-blob checks`,
};
});
@@ -886,7 +915,7 @@ function main(): void {
write(path.join(DOCS, 'JUDGMENT-PARITY.md'), parityDoc(assets.length));
write(path.join(DOCS, 'SCENARIOS.md'), scenarioDoc());
const semantic = runDeterministicSemanticParity(true);
process.stdout.write(`Generated 6 dispatchers, ${sourceRecords.length} modules, ${sectionRecords.length} inlined sections, ${SCENARIOS.length} scenarios, ${BUG_FIX_OVERLAYS.length} bug-fix ports, and ${assets.length} asset copies.\n`);
process.stdout.write(`Generated 6 dispatchers, ${sourceRecords.length} modules, ${sectionRecords.length} lazy specialist sections, ${SCENARIOS.length} scenarios, ${BUG_FIX_OVERLAYS.length} bug-fix ports, and ${assets.length} asset copies.\n`);
process.stdout.write(`Generated semantic evidence: ${semantic.checks} checks across ${semantic.suites} suites and ${semantic.policyUnits} authority-policy unit cases.\n`);
}