@bongos/core 1.19.442 → 1.19.444
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/.bongos-core.json +470 -455
- package/config/branding.neutral.json +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +14 -0
- package/docs/adr/0218-the-apex-root-web-surface.md +55 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +2 -1
- package/docs/copy-inventory.md +360 -360
- package/docs/copy-registry.json +456 -456
- package/docs/design/mocks/hub/mine.html +1 -1
- package/docs/design/mocks/hub/picker.html +1 -1
- package/docs/design/mocks/hub/planets.html +1 -1
- package/docs/design/mocks/hub/v1.html +3 -3
- package/docs/design/mocks/hub/v2.html +2 -2
- package/docs/design/mocks/hub/v3.html +5 -5
- package/docs/design/mocks/hub/wizard.html +1 -1
- package/docs/design/mocks/landing/v1.html +1 -1
- package/docs/design/mocks/landing/v2.html +1 -1
- package/docs/design/mocks/landing/v3.html +1 -1
- package/docs/design/mocks/landing-page-mock.html +3 -3
- package/docs/design/mocks/projects-hub-mock.html +1 -1
- package/docs/design/mocks/projects-hub-variants.html +1 -1
- package/docs/design/mocks/projects-page-mock.html +1 -1
- package/docs/file-map.md +3 -3
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +9 -2
- package/docs/recipes/ui-look-before-you-ship.md +4 -4
- package/modules/copy-desk/tests/copy_flags.mjs +4 -4
- package/modules/public-landing/CLAUDE.md +15 -0
- package/modules/public-landing/module.json +15 -0
- package/modules/ui-design/kit/lib.js +2 -2
- package/modules/ui-design/kit/render.js +1 -1
- package/modules/ui-design/kit/serve.js +3 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/copy-inventory.js +3 -3
- package/scripts/gds/fitness-checks-identity.js +18 -1
- package/scripts/gds/publish-manifest.js +1 -1
- package/scripts/gds/session-digest-build.js +49 -17
- package/scripts/gds/session-digest.js +11 -1
- package/scripts/gds/ship-session-upload.js +102 -12
- package/scripts/gds/ship.js +7 -1
- package/src/bongos/module-scope-map.js +9 -7
- package/src/bongos/serve-internal.js +13 -1
- package/src/module-api.js +1 -1
- package/src/module-loader/loader.js +34 -17
- package/src/module-loader/manifest-schema.js +16 -8
- package/src/platform-server.js +50 -18
- package/tests/copy_inventory.mjs +0 -0
- package/tests/cosmos_token_contrast.mjs +6 -6
- package/tests/credit_grant.mjs +236 -0
- package/tests/fitness.mjs +20 -0
- package/tests/landing_legal_pages.mjs +2 -1
- package/tests/landing_page.mjs +7 -5
- package/tests/module-scope-map.mjs +2 -2
- package/tests/module_loader.mjs +4 -1
- package/tests/onboard.mjs +5 -5
- package/tests/projects_hub.mjs +22 -20
- package/tests/projects_hub_pre_uat.mjs +5 -3
- package/tests/public_landing_projects.mjs +2 -2
- package/tests/terms_acceptance.mjs +1 -1
- package/tests/ui_design_kit.mjs +3 -3
- package/tests/world_tokens.mjs +3 -2
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_00.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_01.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_02.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_03.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_04.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_05.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_06.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_07.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_08.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_09.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_10.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_11.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_12.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_13.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_14.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_15.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_16.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_17.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_18.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_19.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_20.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_21.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_22.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_23.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_24.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_25.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_26.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_27.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_28.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_29.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_30.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_31.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_32.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_33.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_34.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_35.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_36.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_37.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_38.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_39.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_40.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_41.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_42.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/anim/idle/frame_43.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/cosmos.css +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/verro-medallion.png +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/og-card.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-a-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-a-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-bongos-chrome-b.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blob-one.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r2-chrome-blobs-set.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-bead-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-blob-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-frost-pair-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-bead-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-blob-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-pair-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-gold-pair-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-bead-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-bead-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-blob-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-blob-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-pair-cut.webp +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-obsidian-pair-cut.webp.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-space-nebula.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r3-space-nebula.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-chrome.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-chrome.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-deepfield.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-deepfield.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-dust.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-dust.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-nebula.jpg +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world/r4-card-nebula.jpg.json +0 -0
- /package/{public-landing → modules/public-landing/public}/assets/world.css +0 -0
- /package/{public-landing → modules/public-landing/public}/buddha.png +0 -0
- /package/{public-landing → modules/public-landing/public}/contact.html +0 -0
- /package/{public-landing → modules/public-landing/public}/index.html +0 -0
- /package/{public-landing → modules/public-landing/public}/index.probes.json +0 -0
- /package/{public-landing → modules/public-landing/public}/index.states.json +0 -0
- /package/{public-landing → modules/public-landing/public}/privacy.html +0 -0
- /package/{public-landing → modules/public-landing/public}/projects-empty.states.json +0 -0
- /package/{public-landing → modules/public-landing/public}/projects-floor.states.json +0 -0
- /package/{public-landing → modules/public-landing/public}/projects.html +0 -0
- /package/{public-landing → modules/public-landing/public}/projects.probes.json +0 -0
- /package/{public-landing → modules/public-landing/public}/projects.states.json +0 -0
- /package/{public-landing → modules/public-landing/public}/terms.html +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/clouds/cloud-island-large.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/clouds/cloud-puff.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/creatures/creature-whale-cargo.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/creatures/creature-whale-glide.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/plates/plate-clouds-near.png +0 -0
- /package/{public-landing → modules/public-landing/public}/world/assets/plates/scene-establishing.png +0 -0
package/tests/credit_grant.mjs
CHANGED
|
@@ -588,6 +588,242 @@ await test('cost-plus-only cascade: a 0 per-task credit → idea-promotion slice
|
|
|
588
588
|
assert.equal(client.queries.length, 0, 'no lineage lookup, no bonus INSERT when the base credit is 0');
|
|
589
589
|
});
|
|
590
590
|
|
|
591
|
+
// =========================================================================
|
|
592
|
+
// (7) task 1003435 — a PARALLEL session (one claim, several subagents).
|
|
593
|
+
//
|
|
594
|
+
// The cost ledger has always billed a session's subagents subtree in its own
|
|
595
|
+
// `session:subagent` cost_log row, but model_usage — the reward's only input —
|
|
596
|
+
// was built from ONE file, the interactive transcript. So a builder who followed
|
|
597
|
+
// CLAUDE.md's "delegate freely" moved most of a session's real spend into a
|
|
598
|
+
// bucket the reward could not see, and the two ledgers described different
|
|
599
|
+
// sessions.
|
|
600
|
+
//
|
|
601
|
+
// The acceptance test is that RECONCILIATION: what the reward prices must equal
|
|
602
|
+
// what the ledger bills, within independent-rounding epsilon. It runs both real
|
|
603
|
+
// pricing paths over the same on-disk fixture rather than asserting a hand-typed
|
|
604
|
+
// number on either side, so a change to one that is not made to the other reds.
|
|
605
|
+
// =========================================================================
|
|
606
|
+
|
|
607
|
+
const fs = require('node:fs');
|
|
608
|
+
const os = require('node:os');
|
|
609
|
+
const nodePath = require('node:path');
|
|
610
|
+
const { priceTranscript, priceSubagents, buildCostRows } = require('../scripts/gds/session-cost.js');
|
|
611
|
+
const { buildSessionDigest } = require('../scripts/gds/session-digest-build.js');
|
|
612
|
+
const { foldSubagentUsage } = require('../scripts/gds/ship-session-upload.js');
|
|
613
|
+
const { priceModelUsage } = require('../src/bongos/llm-pricing.js');
|
|
614
|
+
|
|
615
|
+
const PARALLEL_SID = 'sess-parallel';
|
|
616
|
+
|
|
617
|
+
// One assistant transcript line. `id` is the API call's identity — two lines
|
|
618
|
+
// sharing one are ONE call, which is what makes the dedup assertion below real.
|
|
619
|
+
function asstLine(id, model, usage) {
|
|
620
|
+
return JSON.stringify({
|
|
621
|
+
type: 'assistant',
|
|
622
|
+
timestamp: '2026-08-30T00:00:00.000Z',
|
|
623
|
+
message: { id, model, usage, content: [{ type: 'text', text: 'work' }] },
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
function userLine(text) {
|
|
627
|
+
return JSON.stringify({
|
|
628
|
+
type: 'user',
|
|
629
|
+
timestamp: '2026-08-30T00:00:00.000Z',
|
|
630
|
+
message: { role: 'user', content: text },
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
function parseJsonlText(raw) {
|
|
634
|
+
return raw.split('\n').filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// A session that spawned three subagents, one of them a grader. Mirrors the real
|
|
638
|
+
// on-disk layout: the interactive transcript is a FILE, and the subagents live in
|
|
639
|
+
// a sibling DIRECTORY named for the session id.
|
|
640
|
+
function makeParallelFixture() {
|
|
641
|
+
const dir = fs.mkdtempSync(nodePath.join(os.tmpdir(), 'otb-parallel-'));
|
|
642
|
+
const mainPath = nodePath.join(dir, `${PARALLEL_SID}.jsonl`);
|
|
643
|
+
// opus: 150k in / 30k out / 1.5M read / 40k write = $0.75+$0.75+$0.75+$0.25 = $2.50.
|
|
644
|
+
// m1 is written twice (one API call, two content blocks) — priced ONCE.
|
|
645
|
+
const dup = asstLine('m1', 'claude-opus-4-8', {
|
|
646
|
+
input_tokens: 100_000, output_tokens: 20_000,
|
|
647
|
+
cache_read_input_tokens: 1_000_000, cache_creation_input_tokens: 40_000,
|
|
648
|
+
});
|
|
649
|
+
fs.writeFileSync(mainPath, [
|
|
650
|
+
userLine('ship task 1003435'),
|
|
651
|
+
dup,
|
|
652
|
+
dup,
|
|
653
|
+
asstLine('m2', 'claude-opus-4-8', {
|
|
654
|
+
input_tokens: 50_000, output_tokens: 10_000,
|
|
655
|
+
cache_read_input_tokens: 500_000, cache_creation_input_tokens: 0,
|
|
656
|
+
}),
|
|
657
|
+
].join('\n'));
|
|
658
|
+
|
|
659
|
+
const subagents = nodePath.join(dir, PARALLEL_SID, 'subagents');
|
|
660
|
+
const nested = nodePath.join(subagents, 'nested');
|
|
661
|
+
fs.mkdirSync(nested, { recursive: true });
|
|
662
|
+
// opus subagent: 200k in / 40k out / 2M read = $1.00+$1.00+$1.00 = $3.00
|
|
663
|
+
fs.writeFileSync(nodePath.join(subagents, 'agent-a.jsonl'), [
|
|
664
|
+
userLine('audit the pricing path'),
|
|
665
|
+
asstLine('a1', 'claude-opus-4-8', {
|
|
666
|
+
input_tokens: 200_000, output_tokens: 40_000, cache_read_input_tokens: 2_000_000,
|
|
667
|
+
}),
|
|
668
|
+
].join('\n'));
|
|
669
|
+
// sonnet subagent, one level deeper — the walk is recursive:
|
|
670
|
+
// 100k in / 20k out / 1M read = $0.20+$0.20+$0.20 = $0.60
|
|
671
|
+
fs.writeFileSync(nodePath.join(nested, 'agent-b.jsonl'), [
|
|
672
|
+
userLine('read the ledger rows'),
|
|
673
|
+
asstLine('b1', 'claude-sonnet-5', {
|
|
674
|
+
input_tokens: 100_000, output_tokens: 20_000, cache_read_input_tokens: 1_000_000,
|
|
675
|
+
}),
|
|
676
|
+
].join('\n'));
|
|
677
|
+
// A GRADER worker nested in the subagents dir. Its $5.00 is already captured via
|
|
678
|
+
// the worker envelope, so pricing it here would double-count (ADR 0028).
|
|
679
|
+
fs.writeFileSync(nodePath.join(nested, 'grader-worker.jsonl'), [
|
|
680
|
+
userLine('You are the **Hacker** — a specialist adversarial subagent. Find the holes.'),
|
|
681
|
+
asstLine('g1', 'claude-opus-4-8', { input_tokens: 1_000_000 }),
|
|
682
|
+
].join('\n'));
|
|
683
|
+
|
|
684
|
+
return { dir, mainPath, sessionDir: nodePath.join(dir, PARALLEL_SID) };
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const fixture = makeParallelFixture();
|
|
688
|
+
const INTERACTIVE_USD = 2.50;
|
|
689
|
+
const SUBAGENT_USD = 3.60;
|
|
690
|
+
const SESSION_USD = INTERACTIVE_USD + SUBAGENT_USD; // $6.10
|
|
691
|
+
// Each cost_log row is rounded to 4dp independently and priceModelUsage rounds
|
|
692
|
+
// its own total, so the two sides may disagree by a rounding step per row.
|
|
693
|
+
const EPSILON = 0.0002;
|
|
694
|
+
|
|
695
|
+
// The digest the ship uploads for this session, subagents folded in.
|
|
696
|
+
function parallelDigest() {
|
|
697
|
+
const digest = buildSessionDigest(parseJsonlText(fs.readFileSync(fixture.mainPath, 'utf8')));
|
|
698
|
+
foldSubagentUsage(digest, fixture.mainPath, PARALLEL_SID);
|
|
699
|
+
return digest;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
await test('1003435 the cost ledger bills interactive + subagent, grader excluded', () => {
|
|
703
|
+
const inter = priceTranscript(fixture.mainPath);
|
|
704
|
+
const sub = priceSubagents(fixture.sessionDir);
|
|
705
|
+
assert.equal(Math.round(inter.total * 100) / 100, INTERACTIVE_USD, 'm1 priced once, not twice');
|
|
706
|
+
assert.equal(Math.round(sub.subagentTotal * 100) / 100, SUBAGENT_USD);
|
|
707
|
+
assert.equal(sub.subagentFiles, 2);
|
|
708
|
+
assert.equal(sub.graderSkipped, 1, 'the grader worker is skipped by signature');
|
|
709
|
+
const built = buildCostRows({ sessionId: PARALLEL_SID, inter, sub });
|
|
710
|
+
assert.equal(built.rows.length, 2, 'one :interactive row + one :subagent row');
|
|
711
|
+
assert.equal(Math.round(built.rowsTotal * 100) / 100, SESSION_USD);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
await test('1003435 model_usage now carries the subagents subtree, grader excluded', () => {
|
|
715
|
+
const digest = parallelDigest();
|
|
716
|
+
const models = Object.keys(digest.metrics.model_usage).sort();
|
|
717
|
+
assert.deepEqual(models, ['claude-opus-4-8', 'claude-sonnet-5'],
|
|
718
|
+
'the sonnet subagent reached model_usage, which only the fold can do');
|
|
719
|
+
assert.equal(digest.metrics.subagent_files, 2);
|
|
720
|
+
assert.equal(digest.metrics.subagent_grader_skipped, 1);
|
|
721
|
+
// The grader's 1M opus input tokens must be in NEITHER ledger.
|
|
722
|
+
assert.equal(digest.metrics.model_usage['claude-opus-4-8'].input, 350_000,
|
|
723
|
+
'150k interactive + 200k subagent — the grader\'s 1M is not counted');
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
await test('1003435 detail + total_tokens stay the INTERACTIVE thread\'s record', () => {
|
|
727
|
+
const interactiveOnly = buildSessionDigest(
|
|
728
|
+
parseJsonlText(fs.readFileSync(fixture.mainPath, 'utf8'))
|
|
729
|
+
);
|
|
730
|
+
const digest = parallelDigest();
|
|
731
|
+
// The fold is model_usage ONLY: detail and the turn/token metrics feed the BFG
|
|
732
|
+
// corpus + inefficiency evaluator, which reason about ONE agent's turn-by-turn
|
|
733
|
+
// behaviour. Splicing N subagent transcripts into that would distort the very
|
|
734
|
+
// signal they measure, so the subagent_* counters carry the difference instead.
|
|
735
|
+
assert.equal(digest.detail.length, interactiveOnly.detail.length);
|
|
736
|
+
assert.equal(digest.totalTokens, interactiveOnly.totalTokens);
|
|
737
|
+
assert.equal(digest.metrics.assistant_turns, interactiveOnly.metrics.assistant_turns);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
await test('1003435 ACCEPTANCE: the reward reconciles with the session\'s cost_log rows', () => {
|
|
741
|
+
const inter = priceTranscript(fixture.mainPath);
|
|
742
|
+
const sub = priceSubagents(fixture.sessionDir);
|
|
743
|
+
const ledgerUsd = buildCostRows({ sessionId: PARALLEL_SID, inter, sub }).rowsTotal;
|
|
744
|
+
const rewardUsd = priceModelUsage(parallelDigest().metrics.model_usage).true_cost_usd;
|
|
745
|
+
assert.ok(Math.abs(rewardUsd - ledgerUsd) <= EPSILON,
|
|
746
|
+
`reward priced $${rewardUsd} but the ledger billed $${ledgerUsd}`);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
await test('1003435 a parallel session is PAID for its subagents', async () => {
|
|
750
|
+
const client = fakeClient();
|
|
751
|
+
client.enqueue(NO_PRIOR);
|
|
752
|
+
client.enqueue([{ id: 99 }]);
|
|
753
|
+
const digest = parallelDigest();
|
|
754
|
+
const res = await awardSessionTokenReward(
|
|
755
|
+
client, 7, PARALLEL_SID, digest.metrics.model_usage, digest.totalTokens
|
|
756
|
+
);
|
|
757
|
+
assert.equal(res.awarded, true);
|
|
758
|
+
assert.equal(res.trueCostUsd, SESSION_USD);
|
|
759
|
+
assert.equal(res.drachmae, Math.round(SESSION_USD * 1.20)); // $6.10 × 1.20 = 7.32 → 7
|
|
760
|
+
// The bug this closes: interactive-only priced the same session at $2.50 → 3.
|
|
761
|
+
const before = computeSessionTokenReward(INTERACTIVE_USD).drachmae;
|
|
762
|
+
assert.ok(res.drachmae > before, `subagent-blind reward was ${before}, now ${res.drachmae}`);
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
await test('1003435 a re-ship of the same session is still a no-op (one award per session)', async () => {
|
|
766
|
+
const digest = parallelDigest();
|
|
767
|
+
const client = fakeClient();
|
|
768
|
+
// The watermark already holds everything this session is worth.
|
|
769
|
+
client.enqueue(ledger(Math.round(SESSION_USD * 1.20), 0));
|
|
770
|
+
const res = await awardSessionTokenReward(
|
|
771
|
+
client, 7, PARALLEL_SID, digest.metrics.model_usage, digest.totalTokens
|
|
772
|
+
);
|
|
773
|
+
assert.equal(res.awarded, false);
|
|
774
|
+
assert.equal(res.drachmae, 0);
|
|
775
|
+
assert.equal(client.queries.length, 1, 'SELECT only — a second upload must not pay again');
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
await test('1003435 a traversing session id folds NOTHING (the walk feeds a payout)', () => {
|
|
779
|
+
// The id is interpolated into a filesystem path whose contents get priced, so
|
|
780
|
+
// a `../` value could aim the walk at another directory and fold fabricated
|
|
781
|
+
// tokens into the reward. The on-ship caller screens the id before resolving
|
|
782
|
+
// the transcript; the SessionEnd hook takes it straight off stdin — so the
|
|
783
|
+
// guard lives in foldSubagentUsage, where every caller gets it.
|
|
784
|
+
const entries = parseJsonlText(fs.readFileSync(fixture.mainPath, 'utf8'));
|
|
785
|
+
const clean = buildSessionDigest(entries).metrics.model_usage;
|
|
786
|
+
for (const hostile of ['../' + PARALLEL_SID, `${PARALLEL_SID}/..`, 'a/../../etc', '']) {
|
|
787
|
+
const digest = foldSubagentUsage(buildSessionDigest(entries), fixture.mainPath, hostile);
|
|
788
|
+
assert.deepEqual(digest.metrics.model_usage, clean, `folded for id ${JSON.stringify(hostile)}`);
|
|
789
|
+
assert.equal(digest.metrics.subagent_files, undefined);
|
|
790
|
+
}
|
|
791
|
+
// …and the legitimate id still folds, so the guard is not just refusing everything.
|
|
792
|
+
const ok = foldSubagentUsage(buildSessionDigest(entries), fixture.mainPath, PARALLEL_SID);
|
|
793
|
+
assert.equal(ok.metrics.subagent_files, 2);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
await test('1003435 usageOnly changes what is BUILT, never what is COUNTED', () => {
|
|
797
|
+
// The fold reuses buildSessionDigest so the token semantics — dedup by
|
|
798
|
+
// message.id, skip <synthetic>, fold unlabeled turns — have ONE home. usageOnly
|
|
799
|
+
// only drops the per-turn rows it would discard (and their snippet scrubbing,
|
|
800
|
+
// which the SessionEnd watchdog gives a hard time budget). If it ever changed a
|
|
801
|
+
// count, the reward and the ledger would quietly diverge again.
|
|
802
|
+
const entries = parseJsonlText(fs.readFileSync(fixture.mainPath, 'utf8'));
|
|
803
|
+
const full = buildSessionDigest(entries);
|
|
804
|
+
const lean = buildSessionDigest(entries, { usageOnly: true });
|
|
805
|
+
assert.deepEqual(lean.metrics.model_usage, full.metrics.model_usage);
|
|
806
|
+
assert.equal(lean.totalTokens, full.totalTokens);
|
|
807
|
+
assert.equal(lean.metrics.assistant_turns, full.metrics.assistant_turns);
|
|
808
|
+
assert.equal(lean.metrics.api_calls, full.metrics.api_calls);
|
|
809
|
+
assert.deepEqual(lean.detail, [], 'the detail rows are what usageOnly skips');
|
|
810
|
+
assert.ok(full.detail.length > 0);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
await test('1003435 a session with no subagents is untouched (no regression)', () => {
|
|
814
|
+
const dir = fs.mkdtempSync(nodePath.join(os.tmpdir(), 'otb-solo-'));
|
|
815
|
+
const mainPath = nodePath.join(dir, 'sess-solo.jsonl');
|
|
816
|
+
fs.writeFileSync(mainPath, asstLine('s1', 'claude-opus-4-8', { input_tokens: 110_000 }));
|
|
817
|
+
const entries = parseJsonlText(fs.readFileSync(mainPath, 'utf8'));
|
|
818
|
+
const before = buildSessionDigest(entries);
|
|
819
|
+
const after = foldSubagentUsage(buildSessionDigest(entries), mainPath, 'sess-solo');
|
|
820
|
+
assert.deepEqual(after.metrics.model_usage, before.metrics.model_usage);
|
|
821
|
+
assert.equal(after.metrics.subagent_files, undefined, 'no counters when there is nothing to count');
|
|
822
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
fs.rmSync(fixture.dir, { recursive: true, force: true });
|
|
826
|
+
|
|
591
827
|
// -------------------------------------------------------------------------
|
|
592
828
|
// runner summary
|
|
593
829
|
// -------------------------------------------------------------------------
|
package/tests/fitness.mjs
CHANGED
|
@@ -660,6 +660,26 @@ test('identity gate skips comments + instance-injected tokens (no false positive
|
|
|
660
660
|
assert.equal(scanPromptIdentity('x.html', '<title>{{worldName}} — the gate</title>').length, 0, 'a {{worldName}} token carries no literal');
|
|
661
661
|
});
|
|
662
662
|
|
|
663
|
+
// task 1003322 / ADR 0218 — the front door became modules/public-landing/, which put
|
|
664
|
+
// the landing pages under this gate for the first time. Its apex-only regions name
|
|
665
|
+
// the projects HOSTED on the hub; the server deletes them on every non-hub instance
|
|
666
|
+
// (stripApexOnly), so they cannot produce the failure this gate names — "a non-OTB
|
|
667
|
+
// instance renders ITS OWN brand". Everything outside the sentinels still fails.
|
|
668
|
+
test('identity gate: an apex-only region is exempt, and only that region (ADR 0218)', () => {
|
|
669
|
+
const { scanPromptIdentity } = fitness;
|
|
670
|
+
const inside = '<ul>\n<!--apex-only:start-->\n<li>Off the Boats</li>\n<!--apex-only:end-->\n</ul>';
|
|
671
|
+
assert.equal(scanPromptIdentity('x.html', inside).length, 0, 'a literal the server strips on a child instance is exempt');
|
|
672
|
+
|
|
673
|
+
const outside = '<h1>Off the Boats</h1>\n<!--apex-only:start-->\n<li>ok</li>\n<!--apex-only:end-->';
|
|
674
|
+
const hits = scanPromptIdentity('x.html', outside);
|
|
675
|
+
assert.equal(hits.length, 1, 'a literal OUTSIDE the sentinels still fails — the exemption is the region, not the file');
|
|
676
|
+
assert.match(hits[0], /^x\.html:1 /, 'blanking preserves line numbers so the violation still points at the real line');
|
|
677
|
+
|
|
678
|
+
// an UNCLOSED start sentinel must not blank the rest of the file into silence
|
|
679
|
+
const unclosed = '<!--apex-only:start-->\n<li>ok</li>\n<h1>Off the Boats</h1>';
|
|
680
|
+
assert.equal(scanPromptIdentity('x.html', unclosed).length, 1, 'an unterminated region exempts nothing');
|
|
681
|
+
});
|
|
682
|
+
|
|
663
683
|
// task 1002756 — the two holes that let migrate.sh's founder DB default survive:
|
|
664
684
|
// (1) the stripper could be OPENED by a `/*` inside a `//` line comment (a real
|
|
665
685
|
// `*/` hundreds of lines later then swallowed everything between — provision.js
|
|
@@ -22,7 +22,8 @@ import { createRequire } from 'node:module';
|
|
|
22
22
|
|
|
23
23
|
const require = createRequire(import.meta.url);
|
|
24
24
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
-
|
|
25
|
+
// the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
|
|
26
|
+
const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
|
|
26
27
|
const read = (f) => fs.readFileSync(path.join(LANDING, f), 'utf8');
|
|
27
28
|
|
|
28
29
|
const platformMod = require('../src/platform-server.js');
|
package/tests/landing_page.mjs
CHANGED
|
@@ -40,8 +40,10 @@ import { createRequire } from 'node:module';
|
|
|
40
40
|
|
|
41
41
|
const require = createRequire(import.meta.url);
|
|
42
42
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
43
|
-
|
|
44
|
-
const
|
|
43
|
+
// the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
|
|
44
|
+
const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
|
|
45
|
+
const HTML = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
|
|
46
|
+
const WORLD = fs.readFileSync(path.join(LANDING, 'assets', 'world.css'), 'utf8');
|
|
45
47
|
|
|
46
48
|
const { stripApexOnly } = require('../src/platform-server.js');
|
|
47
49
|
const { applyBrandTokens } = require('../src/bongos/serve-internal.js');
|
|
@@ -186,8 +188,8 @@ test('the social card is the world card, stamped onto THIS instance', () => {
|
|
|
186
188
|
assert.equal(m[1], 'https://spike-harbor.example/assets/world/og-card.jpg');
|
|
187
189
|
}
|
|
188
190
|
assert.ok(
|
|
189
|
-
fs.existsSync(path.join(
|
|
190
|
-
'the og card the head names must exist under
|
|
191
|
+
fs.existsSync(path.join(LANDING, 'assets', 'world', 'og-card.jpg')),
|
|
192
|
+
'the og card the head names must exist under the landing surface',
|
|
191
193
|
);
|
|
192
194
|
});
|
|
193
195
|
|
|
@@ -210,7 +212,7 @@ test('a pack with no hall of its own falls back to its own apex, not the platfor
|
|
|
210
212
|
|
|
211
213
|
test('a pack with NO domains drops the hall pill server-side rather than stamping href="" (task 1003242)', () => {
|
|
212
214
|
// the hub has no client scrubber, so an empty token must never reach it as a dead link
|
|
213
|
-
const HUB = fs.readFileSync(path.join(
|
|
215
|
+
const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8');
|
|
214
216
|
assert.match(HUB, /data-link-hall href="\{\{buildersOrigin\}\}"/, 'the hub pill is token-stamped');
|
|
215
217
|
const none = applyBrandTokens(HUB, BRAND, '', '');
|
|
216
218
|
assert.doesNotMatch(none, /href=""/, 'no empty href survives the stamp');
|
|
@@ -100,7 +100,7 @@ test('provisioning + public-landing joined the roster (task 1002690) — territo
|
|
|
100
100
|
const prov = scope.globsForModules(['provisioning']);
|
|
101
101
|
assert.ok(prov.includes('modules/provisioning/'), 'provisioning owns its module directory');
|
|
102
102
|
assert.ok(prov.includes('scripts/gds/provision.js'), 'the control-plane runner travels with its domain');
|
|
103
|
-
assert.ok(scope.globsForModules(['public-landing']).includes('public-landing/'), 'public-landing owns its surface directory');
|
|
103
|
+
assert.ok(scope.globsForModules(['public-landing']).includes('modules/public-landing/'), 'public-landing owns its surface directory');
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
test('platform-identity joined the roster (task 1002322) — the catalog territory goal 1000046 walls', () => {
|
|
@@ -127,7 +127,7 @@ test('platform-identity joined the roster (task 1002322) — the catalog territo
|
|
|
127
127
|
'modules/platform-identity/routes/projects.js', // the feed + invite routes
|
|
128
128
|
'modules/status-ui/public/explore.js', // orb render-type surface
|
|
129
129
|
'modules/hall-ui/public/my-projects.js', // owner settings / applications queue
|
|
130
|
-
'public-landing/projects.html', // the creation step, ?view=new since task 1002763
|
|
130
|
+
'modules/public-landing/public/projects.html', // the creation step, ?view=new since task 1002763
|
|
131
131
|
'modules/provisioning/', // module-bundle selection at provision time
|
|
132
132
|
]) {
|
|
133
133
|
assert.equal(covers(touch), true, `wall does not cover ${touch}`);
|
package/tests/module_loader.mjs
CHANGED
|
@@ -179,6 +179,9 @@ test('the live tree discovers the extracted modules cleanly (BV1.R43/R48/R47/R49
|
|
|
179
179
|
// And ui-design (task 1003324 / ADR 0197) — the design contract, its adapters
|
|
180
180
|
// and the /design playbook as a platform capability: declaration-only (three
|
|
181
181
|
// skills, no routes/port), default-on because every instance has a face.
|
|
182
|
+
// And public-landing (task 1003322 / ADR 0218) — the front door, the THIRD web
|
|
183
|
+
// surface after status-ui and hall-ui and the first to declare apexRoot instead
|
|
184
|
+
// of a Host prefix; default-on because every instance answers at its own apex.
|
|
182
185
|
// All are discovered; none has validation
|
|
183
186
|
// errors or coreVersion mismatches. Order is the readdir sort.
|
|
184
187
|
//
|
|
@@ -203,7 +206,7 @@ test('the live tree discovers the extracted modules cleanly (BV1.R43/R48/R47/R49
|
|
|
203
206
|
// That is precisely how the agents regression reached an open PR (task 1002485).
|
|
204
207
|
loader._reset();
|
|
205
208
|
const snap = loader.loaderSnapshot();
|
|
206
|
-
assert.deepEqual(snap.loaded, ['agents', 'autonomy', 'builder-settings', 'copy-desk', 'dev-box', 'discord', 'economy', 'government', 'grading', 'hall-ui', 'ideas', 'lifecycle', 'memory', 'onboarding', 'platform-identity', 'provisioning', 'security', 'sessions', 'status-ui', 'ui-design']);
|
|
209
|
+
assert.deepEqual(snap.loaded, ['agents', 'autonomy', 'builder-settings', 'copy-desk', 'dev-box', 'discord', 'economy', 'government', 'grading', 'hall-ui', 'ideas', 'lifecycle', 'memory', 'onboarding', 'platform-identity', 'provisioning', 'public-landing', 'security', 'sessions', 'status-ui', 'ui-design']);
|
|
207
210
|
assert.deepEqual(snap.errors, []);
|
|
208
211
|
assert.deepEqual(snap.skipped, []);
|
|
209
212
|
loader._reset();
|
package/tests/onboard.mjs
CHANGED
|
@@ -64,7 +64,7 @@ test('every form OFFERS the optional step and names what skipping costs — skil
|
|
|
64
64
|
assert.match(cli, /off the public map/, 'and says what a missing description means');
|
|
65
65
|
|
|
66
66
|
// the web form
|
|
67
|
-
const hub = read('public-landing/projects.html');
|
|
67
|
+
const hub = read('modules/public-landing/public/projects.html');
|
|
68
68
|
assert.match(hub, /id="panel-detail"/, 'the wizard ships the panel');
|
|
69
69
|
assert.match(hub, /id="skipDetail"/, 'with a skip control of its own');
|
|
70
70
|
|
|
@@ -168,7 +168,7 @@ test('every render surface carries the terminal hall step (task 1002703)', () =>
|
|
|
168
168
|
const cli = read('scripts/gds/onboard.js');
|
|
169
169
|
assert.match(cli, /builders hall/i, 'the CLI walkthrough ends at the hall');
|
|
170
170
|
assert.match(cli, /bongos dev/, 'the CLI offers the local-hall one-liner when no domain');
|
|
171
|
-
const hubHtml = read('public-landing/projects.html');
|
|
171
|
+
const hubHtml = read('modules/public-landing/public/projects.html');
|
|
172
172
|
const enterHall = hubHtml.match(/data-onboard-step="enter-hall"[^]*?<\/li>/g) || [];
|
|
173
173
|
assert.ok(enterHall.length >= 1, 'the ?view=new wizard tags the enter-hall step');
|
|
174
174
|
assert.match(enterHall.join(''), /\/builders/, 'the wizard points at the hall address');
|
|
@@ -180,7 +180,7 @@ test('the wizard done panel is plan-driven, manifest-driven, and owner-honest (t
|
|
|
180
180
|
// The wizard lives inside the hub page since task 1002763.
|
|
181
181
|
const { readFileSync } = require('node:fs');
|
|
182
182
|
const path = require('node:path');
|
|
183
|
-
const src = readFileSync(path.join(import.meta.dirname, '..', 'public-landing/projects.html'), 'utf8');
|
|
183
|
+
const src = readFileSync(path.join(import.meta.dirname, '..', 'modules/public-landing/public/projects.html'), 'utf8');
|
|
184
184
|
// gap 2 (idea 1000675): the checklist renders FROM the canonical plan endpoint
|
|
185
185
|
assert.match(src, /provisioning\/onboard-plan/, 'the done panel fetches the canonical plan');
|
|
186
186
|
// gap 3 (idea 1000676): the ADR 0133 one-click manifest flow has a UI caller again
|
|
@@ -203,7 +203,7 @@ test('the wizard gate trust link points at a live artifact, not the dead private
|
|
|
203
203
|
// "open source" is the license itself.
|
|
204
204
|
const { readFileSync } = require('node:fs');
|
|
205
205
|
const path = require('node:path');
|
|
206
|
-
const src = readFileSync(path.join(import.meta.dirname, '..', 'public-landing/projects.html'), 'utf8');
|
|
206
|
+
const src = readFileSync(path.join(import.meta.dirname, '..', 'modules/public-landing/public/projects.html'), 'utf8');
|
|
207
207
|
assert.doesNotMatch(src, /Claude-OTB/, 'the dead repo link is gone');
|
|
208
208
|
assert.match(src, /https:\/\/www\.gnu\.org\/licenses\/agpl-3\.0/, 'the trust link resolves (the AGPL-3.0 license text)');
|
|
209
209
|
});
|
|
@@ -266,7 +266,7 @@ test('the web form (?view=new) declares EXACTLY the canonical human steps — dr
|
|
|
266
266
|
const path = require('node:path');
|
|
267
267
|
const humanKeys = ob.onboardPlan().filter((s) => s.kind === 'human').map((s) => s.key).sort();
|
|
268
268
|
const surfaces = {
|
|
269
|
-
'/projects?view=new wizard': 'public-landing/projects.html',
|
|
269
|
+
'/projects?view=new wizard': 'modules/public-landing/public/projects.html',
|
|
270
270
|
};
|
|
271
271
|
for (const [name, rel] of Object.entries(surfaces)) {
|
|
272
272
|
const src = readFileSync(path.join(import.meta.dirname, '..', rel), 'utf8');
|
package/tests/projects_hub.mjs
CHANGED
|
@@ -19,8 +19,10 @@ import { fileURLToPath } from 'node:url';
|
|
|
19
19
|
import { createRequire } from 'node:module';
|
|
20
20
|
|
|
21
21
|
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
|
-
|
|
23
|
-
const
|
|
22
|
+
// the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
|
|
23
|
+
const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
|
|
24
|
+
const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8');
|
|
25
|
+
const COSMOS = fs.readFileSync(path.join(LANDING, 'assets', 'cosmos.css'), 'utf8');
|
|
24
26
|
const STYLE = (HUB.match(/<style>([\s\S]*?)<\/style>/) || [, ''])[1];
|
|
25
27
|
const SCRIPT = (HUB.match(/<script>([\s\S]*?)<\/script>\s*<\/body>/) || [, ''])[1];
|
|
26
28
|
|
|
@@ -71,7 +73,7 @@ test('?view= is the deep-link contract, read on boot and written with replaceSta
|
|
|
71
73
|
assert.match(SCRIPT, /'&id=' \+ encodeURIComponent\(manageId\)/,
|
|
72
74
|
'the URL writer must carry the manage id, encoded');
|
|
73
75
|
// the landing page's CTAs have always pointed here — this is the bug they hit
|
|
74
|
-
const landing = fs.readFileSync(path.join(
|
|
76
|
+
const landing = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
|
|
75
77
|
assert.match(landing, /href="\/projects\?view=new"/,
|
|
76
78
|
'the landing CTA target must stay a view the hub knows');
|
|
77
79
|
});
|
|
@@ -255,8 +257,8 @@ test('the gate is retired — no route, no files, no links into it', () => {
|
|
|
255
257
|
// the FILES, not the directory: git cannot track an empty dir, so a stale
|
|
256
258
|
// working tree may keep the folder while the commit is clean
|
|
257
259
|
for (const f of ['index.html', 'space.js', 'space.css']) {
|
|
258
|
-
assert.equal(fs.existsSync(path.join(
|
|
259
|
-
`
|
|
260
|
+
assert.equal(fs.existsSync(path.join(LANDING, 'gate', f)), false,
|
|
261
|
+
`the landing surface’s gate/${f} must be gone: express.static would keep serving it`);
|
|
260
262
|
}
|
|
261
263
|
const server = fs.readFileSync(path.join(ROOT, 'src', 'platform-server.js'), 'utf8');
|
|
262
264
|
assert.doesNotMatch(server, /res\.redirect\(302, '\/gate\/'\)/,
|
|
@@ -272,7 +274,7 @@ test('/create is absorbed — the wizard lives here and the route only redirects
|
|
|
272
274
|
// are real markup inside the new view (C7), create.html is deleted — the
|
|
273
275
|
// gate precedent above: express.static would keep serving the raw file —
|
|
274
276
|
// and the name handoff became an in-page step advance.
|
|
275
|
-
assert.equal(fs.existsSync(path.join(
|
|
277
|
+
assert.equal(fs.existsSync(path.join(LANDING, 'create.html')), false,
|
|
276
278
|
'create.html must be gone: express.static would keep serving it raw and unbranded');
|
|
277
279
|
assert.doesNotMatch(SCRIPT, /location\.assign\('\/create'/,
|
|
278
280
|
'naming a project advances the wizard in place — no navigation');
|
|
@@ -449,8 +451,8 @@ test('the count includes the platform when it is shown', () => {
|
|
|
449
451
|
and the hub's. ── */
|
|
450
452
|
|
|
451
453
|
test('neither the landing hero nor the hub types (tasks 1003298, 1003300)', () => {
|
|
452
|
-
const
|
|
453
|
-
assert.doesNotMatch(
|
|
454
|
+
const LANDING_HTML = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
|
|
455
|
+
assert.doesNotMatch(LANDING_HTML, /id="typed"|setTimeout\(type\b/, 'the landing hero does not type');
|
|
454
456
|
assert.doesNotMatch(SCRIPT, /function typeMark\(\)|HOLD_HOME|setTimeout\(type\b/, 'the hub has no typing loop');
|
|
455
457
|
});
|
|
456
458
|
|
|
@@ -1569,9 +1571,9 @@ test('the walk assigns the template by canonical position beside the accent; a c
|
|
|
1569
1571
|
});
|
|
1570
1572
|
|
|
1571
1573
|
test("the planet is the landing's object on the landing's crop maths: the three rectangles are byte-equal across the two sheets", () => {
|
|
1572
|
-
const
|
|
1574
|
+
const LANDING_HTML = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8');
|
|
1573
1575
|
const landingCrop = (cls) => {
|
|
1574
|
-
const m =
|
|
1576
|
+
const m = LANDING_HTML.match(new RegExp(`\\.${cls}\\{[^}]*--bead-ar:([^;]+);--bead-zoom:([^;]+);--bead-px:([^;]+);--bead-py:([^;}]+)`));
|
|
1575
1577
|
assert.ok(m, `the landing declares .${cls}`);
|
|
1576
1578
|
return m.slice(1, 5).map((s) => s.trim());
|
|
1577
1579
|
};
|
|
@@ -1604,7 +1606,7 @@ test('the materials (task 1003337): a whole object per plate, the crop recompute
|
|
|
1604
1606
|
const rule = `.worldOrb[data-material="${material}"][data-shape="${shape}"]{--tpl-src:var(--plate-${material}-${shape}-cut);--tpl-ar:${f5(w / h)};--tpl-zoom:${f5(zoom)};--tpl-px:${f5(-U0 * zoom)};--tpl-py:${f5(-V0 * zoom)};}`;
|
|
1605
1607
|
assert.ok(COSMOS.includes(rule), `the ${key} crop follows from its box: ${rule}`);
|
|
1606
1608
|
assert.ok(COSMOS.includes(`--plate-${material}-${shape}-cut:url(/assets/world/r3-${material}-${shape}-cut.webp);`), `the ${key} plate token`);
|
|
1607
|
-
const file = path.join(
|
|
1609
|
+
const file = path.join(LANDING, 'assets', 'world', `r3-${material}-${shape}-cut.webp`);
|
|
1608
1610
|
assert.ok(fs.existsSync(file), `${key}: the cutout ships`);
|
|
1609
1611
|
assert.ok(fs.existsSync(file + '.json'), `${key}: with its sidecar`);
|
|
1610
1612
|
const head = fs.readFileSync(file).subarray(0, 40).toString('latin1');
|
|
@@ -1620,7 +1622,7 @@ test('the field carries a rendered backdrop (task 1003337): one token, cover, a
|
|
|
1620
1622
|
assert.match(COSMOS, /\.field \.ground\{position:absolute;inset:-4%;background:var\(--sky-bg\) var\(--field-plate\) center\/cover no-repeat;\}/);
|
|
1621
1623
|
assert.match(COSMOS, /@media \(prefers-reduced-motion:no-preference\)\{\s*\.field \.ground\{animation:groundDrift 90s ease-in-out infinite alternate;\}/, 'the drift only runs where motion is welcome');
|
|
1622
1624
|
assert.match(COSMOS, /@keyframes groundDrift\{from\{transform:translate3d\([^)]*\) scale\(1\.02\);\}to\{transform:translate3d\([^)]*\) scale\(1\.06\);\}\}/, 'transform only');
|
|
1623
|
-
const plate = path.join(
|
|
1625
|
+
const plate = path.join(LANDING, 'assets', 'world', 'r3-space-nebula.jpg');
|
|
1624
1626
|
assert.ok(fs.existsSync(plate) && fs.existsSync(plate + '.json'), 'the plate and its sidecar ship');
|
|
1625
1627
|
const side = JSON.parse(fs.readFileSync(plate + '.json', 'utf8'));
|
|
1626
1628
|
assert.equal(side.kind, 'backdrop');
|
|
@@ -1680,7 +1682,7 @@ test('the kit proves the field path: the fixture carries a curated template and
|
|
|
1680
1682
|
assert.equal(demo.planet_template, 'pair-obsidian-tint', 'the owner\'s choice rides the same wire (task 1003347)');
|
|
1681
1683
|
assert.equal(demo.planet_accent, 'sea', 'a token NAME, never a colour');
|
|
1682
1684
|
assert.ok(fixture.projects.every((p) => 'template' in p && 'planet_template' in p && 'planet_accent' in p), 'the wire never drops a key');
|
|
1683
|
-
const states = JSON.parse(fs.readFileSync(path.join(
|
|
1685
|
+
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8'));
|
|
1684
1686
|
const attrs = states.states['in-map'].expect.attr.map((a) => a.join(' '));
|
|
1685
1687
|
assert.ok(attrs.includes('#selfOrb data-template pair-chrome'));
|
|
1686
1688
|
assert.ok(attrs.some((a) => a.includes('amazonprimea.com') && a.endsWith('data-template bead-wash')), 'the curated template reaches the orb');
|
|
@@ -1756,7 +1758,7 @@ test('the picker wears the world by scope: the preview left and four bands right
|
|
|
1756
1758
|
});
|
|
1757
1759
|
|
|
1758
1760
|
test('the kit proves the picker: a state renders it on the owner\'s feed-carried choice, three probes save / hand back / fold, and both fixtures carry the choice (task 1003347)', () => {
|
|
1759
|
-
const states = JSON.parse(fs.readFileSync(path.join(
|
|
1761
|
+
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8'));
|
|
1760
1762
|
const st = states.states['in-manage-planet'];
|
|
1761
1763
|
assert.ok(st && st.auth && st.url === '/projects?view=manage&id=101', 'the signed-in owner on Demo');
|
|
1762
1764
|
assert.ok(st.expect.visible.includes('#mPlanetPick') && st.expect.visible.includes('#mPick [data-pick-reset]'));
|
|
@@ -1770,7 +1772,7 @@ test('the kit proves the picker: a state renders it on the owner\'s feed-carried
|
|
|
1770
1772
|
const harbor = inst.instances.find((i) => i.slug === 'harbor');
|
|
1771
1773
|
assert.equal(harbor.planet_template, 'blob-frost-tint');
|
|
1772
1774
|
assert.equal(harbor.planet_accent, 'sun-lit');
|
|
1773
|
-
const probes = JSON.parse(fs.readFileSync(path.join(
|
|
1775
|
+
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8'));
|
|
1774
1776
|
const names = probes.probes.map((p) => p.name);
|
|
1775
1777
|
assert.ok(names.some((n) => n.startsWith('manage flows: its planet (task 1003347)')), 'the save probe');
|
|
1776
1778
|
assert.ok(names.some((n) => n.includes('handed back to the platform')), 'the reset probe');
|
|
@@ -1884,7 +1886,7 @@ test("the card paints the owner's plate before the project's own picture, and a
|
|
|
1884
1886
|
|
|
1885
1887
|
test('every plate the picker offers ships as a real file with its provenance sidecar', () => {
|
|
1886
1888
|
for (const k of ['nebula', 'dust', 'deepfield', 'chrome']) {
|
|
1887
|
-
const file = path.join(
|
|
1889
|
+
const file = path.join(LANDING, 'assets', 'world', `r4-card-${k}.jpg`);
|
|
1888
1890
|
assert.ok(fs.existsSync(file), `r4-card-${k}.jpg must ship — the picker offers it`);
|
|
1889
1891
|
const side = JSON.parse(fs.readFileSync(`${file}.json`, 'utf8'));
|
|
1890
1892
|
assert.equal(side.task, 1003352, 'the sidecar names the task that made it');
|
|
@@ -1902,9 +1904,9 @@ test("its card is the planet section's twin: its own heading, the same channel,
|
|
|
1902
1904
|
assert.match(SCRIPT, /manageInst\.card_backdrop = \(data\.planet \|\| \{\}\)\.card_backdrop \|\| null;/,
|
|
1903
1905
|
'the reply is a receipt — the page repaints from the row, never from words in the reply');
|
|
1904
1906
|
assert.match(SCRIPT, /renderPick\(inst\);\r?\n\s*renderCardPick\(inst\);/, 'both sections fill from the same manage read');
|
|
1905
|
-
const states = JSON.parse(fs.readFileSync(path.join(
|
|
1907
|
+
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8'));
|
|
1906
1908
|
assert.ok(states.states['in-manage-card'], 'the section has a rendered state');
|
|
1907
|
-
const probes = JSON.parse(fs.readFileSync(path.join(
|
|
1909
|
+
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8'));
|
|
1908
1910
|
const names = probes.probes.map((p) => p.name);
|
|
1909
1911
|
assert.ok(names.some((n) => n.startsWith('manage flows: its card (task 1003352)')), 'the save probe');
|
|
1910
1912
|
assert.ok(names.some((n) => n.includes("its card — handing it back")), 'the hand-back probe');
|
|
@@ -1960,8 +1962,8 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
|
|
|
1960
1962
|
assert.match(SCRIPT, /target_ref: state\.owner \+ '\/' \+ state\.repo,/, 'the repo ref is unchanged');
|
|
1961
1963
|
assert.match(SCRIPT, /onboard_mode: state\.mode === 'adopt' \? 'adopt' : 'greenfield'/, 'the onboard mode is unchanged');
|
|
1962
1964
|
// the kit walks every step and files against the stub
|
|
1963
|
-
const states = JSON.parse(fs.readFileSync(path.join(
|
|
1965
|
+
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8')).states;
|
|
1964
1966
|
for (const s of ['type', 'about', 'home', 'address', 'review', 'done']) assert.ok(states['in-new-' + s], `the ${s} step has a kit state`);
|
|
1965
|
-
const probes = JSON.parse(fs.readFileSync(path.join(
|
|
1967
|
+
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
|
|
1966
1968
|
assert.ok(probes.some((p) => /the wizard driver \(task 1003346\)/.test(p.name) && p.steps.some((st) => st[1] === '#doCreate')), 'the wizard driver files a project end to end');
|
|
1967
1969
|
});
|
|
@@ -11,11 +11,13 @@ import path from 'node:path';
|
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
|
|
13
13
|
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
14
|
+
// the front door is a module since task 1003322 (ADR 0218) — one constant, so the path moves once
|
|
15
|
+
const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
|
|
14
16
|
// LF-normalised: a Windows checkout (core.autocrlf) hands back CRLF, and the
|
|
15
17
|
// anchored regexes below reason about line ends
|
|
16
|
-
const HUB = fs.readFileSync(path.join(
|
|
17
|
-
const COSMOS = fs.readFileSync(path.join(
|
|
18
|
-
const INDEX = fs.readFileSync(path.join(
|
|
18
|
+
const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
|
|
19
|
+
const COSMOS = fs.readFileSync(path.join(LANDING, 'assets', 'cosmos.css'), 'utf8').replace(/\r\n/g, '\n');
|
|
20
|
+
const INDEX = fs.readFileSync(path.join(LANDING, 'index.html'), 'utf8').replace(/\r\n/g, '\n');
|
|
19
21
|
const SCRIPT = HUB.slice(HUB.lastIndexOf('<script>'), HUB.lastIndexOf('</script>'));
|
|
20
22
|
|
|
21
23
|
// a function's text runs from its declaration to the next declaration in the
|
|
@@ -470,7 +470,7 @@ test('the stamp is derived from CONTENT, so it changes only when the file does',
|
|
|
470
470
|
const cryptoMod = await import('node:crypto');
|
|
471
471
|
const url = await import('node:url');
|
|
472
472
|
const root = pathMod.dirname(url.fileURLToPath(import.meta.url));
|
|
473
|
-
const css = fsMod.readFileSync(pathMod.join(root, '..', 'public-landing', 'assets', 'cosmos.css'));
|
|
473
|
+
const css = fsMod.readFileSync(pathMod.join(root, '..', 'modules', 'public-landing', 'public', 'assets', 'cosmos.css'));
|
|
474
474
|
const want = cryptoMod.createHash('sha256').update(css).digest('hex').slice(0, 8);
|
|
475
475
|
const res = await request('/projects');
|
|
476
476
|
assert.ok(res.body.includes(`cosmos.css?v=${want}`),
|
|
@@ -488,7 +488,7 @@ test('every landing surface is stamped, not just the one that broke', async () =
|
|
|
488
488
|
});
|
|
489
489
|
|
|
490
490
|
test('routes and API paths are never mistaken for assets', async () => {
|
|
491
|
-
// stamping resolves the URL against
|
|
491
|
+
// stamping resolves the URL against the surface dir — a route names no file,
|
|
492
492
|
// so it must come back untouched rather than acquiring a meaningless ?v=
|
|
493
493
|
const res = await request('/projects');
|
|
494
494
|
assert.match(res.body, /href="\/"/, "'/' is a route, not an asset");
|
|
@@ -94,7 +94,7 @@ async function ta(name, fn) {
|
|
|
94
94
|
|
|
95
95
|
console.log('the page (/terms carries the consent the ADR deferred to it):');
|
|
96
96
|
|
|
97
|
-
const termsHtml = fs.readFileSync(path.join(repo, 'public-landing', 'terms.html'), 'utf8');
|
|
97
|
+
const termsHtml = fs.readFileSync(path.join(repo, 'modules', 'public-landing', 'public', 'terms.html'), 'utf8');
|
|
98
98
|
const { stripApexOnly } = require('../src/platform-server.js');
|
|
99
99
|
|
|
100
100
|
t('the terms page states what crosses between projects', () => {
|
package/tests/ui_design_kit.mjs
CHANGED
|
@@ -33,7 +33,7 @@ test('the platform floors are a pinned list — a floor cannot vanish quietly',
|
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
test('the shipped states files validate and sit beside the page they name', () => {
|
|
36
|
-
for (const rel of ['public-landing/projects.states.json', 'public-landing/projects-empty.states.json', 'public-landing/index.states.json', 'modules/hall-ui/public/ranks.states.json']) {
|
|
36
|
+
for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/projects-empty.states.json', 'modules/public-landing/public/index.states.json', 'modules/hall-ui/public/ranks.states.json']) {
|
|
37
37
|
const json = readJson(rel);
|
|
38
38
|
const v = lib.validateStatesFile(json);
|
|
39
39
|
assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);
|
|
@@ -42,14 +42,14 @@ test('the shipped states files validate and sit beside the page they name', () =
|
|
|
42
42
|
}
|
|
43
43
|
// the apex is dark only (task 1003336): its states files name the one mode, and
|
|
44
44
|
// render.js reads it as the default so a plain `--page /` never renders light
|
|
45
|
-
for (const rel of ['public-landing/projects.states.json', 'public-landing/index.states.json']) {
|
|
45
|
+
for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/index.states.json']) {
|
|
46
46
|
assert.deepEqual(readJson(rel).modes, ['dark'], `${rel}: the apex renders dark only`);
|
|
47
47
|
assert.equal(readJson(rel).modeQuery, false, `${rel}: no query walks a second mode`);
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
test('the shipped probes files validate', () => {
|
|
52
|
-
for (const rel of ['public-landing/projects.probes.json', 'public-landing/index.probes.json']) {
|
|
52
|
+
for (const rel of ['modules/public-landing/public/projects.probes.json', 'modules/public-landing/public/index.probes.json']) {
|
|
53
53
|
const v = lib.validateProbesFile(readJson(rel));
|
|
54
54
|
assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);
|
|
55
55
|
}
|