Skip to content

Commit d792bfe

Browse files
author
DavidQ
committed
Enforce explicit tool input contract (payload + optional palette only) - PR 11.132
1 parent 2e6aaac commit d792bfe

6 files changed

Lines changed: 214 additions & 120 deletions

File tree

docs/dev/codex_commands.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ ALLOWED FILES:
1010

1111
TASK:
1212

13-
1. Find palette usage
14-
2. Remove:
15-
- palette injection
16-
- palette merging
17-
- defaults
18-
3. Ensure separate pass-through
13+
1. Find tool launch calls
14+
2. Enforce signature:
15+
launch(toolId, payloadJson, paletteJson?)
16+
17+
3. Remove:
18+
- implicit/global input
19+
- hidden dependencies
1920

2021
VERIFY:
21-
payload unchanged
22-
palette unchanged
22+
- inputs are explicit only
2323

2424
REPORT:
25-
docs/dev/reports/palette_pass_through_11_131.txt
25+
docs/dev/reports/final_tool_input_contract_11_132.txt
2626

27-
FAIL if mutation exists
27+
FAIL if ambiguity remains

docs/dev/commit_comment.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Enforce palette pass-through routing without mutation - PR 11.131
1+
Enforce explicit tool input contract (payload + optional palette only) - PR 11.132
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
Final Tool Input Contract Report: 11_132
2+
Date: 2026-04-30
3+
Repo: C:/Users/davidq/Documents/GitHub/HTML-JavaScript-Gaming
4+
Mode: STRICT SCOPE
5+
6+
Objective
7+
- Enforce explicit launch signature at routing boundary:
8+
launch(toolId, payloadJson, paletteJson?)
9+
- Remove implicit/global launch inputs and hidden dependencies.
10+
11+
Routing files changed
12+
- tools/shared/toolHostRuntime.js
13+
- tools/Workspace Manager/main.js
14+
15+
Changes
16+
1) Tool launch API contract
17+
- Replaced host runtime launch entrypoint with explicit signature:
18+
function launch(toolId, payloadJson, paletteJson = null)
19+
- Removed mount-style config launch API and implicit config forwarding.
20+
21+
2) Explicit-only input enforcement
22+
- Runtime now blocks launch when payloadJson is missing/non-object.
23+
- Runtime now blocks launch when paletteJson is provided but not an object.
24+
25+
3) Hidden/implicit input removal
26+
- Removed launch URL query forwarding from runtime launch path.
27+
- Removed Workspace Manager forwarded-input reader used for implicit launch params.
28+
- Workspace Manager now resolves payloadJson explicitly from workspace manifest tool entries.
29+
- Workspace Manager passes paletteJson explicitly from workspace manifest palette entry when present.
30+
- Workspace Manager blocks launch when explicit payloadJson is unavailable.
31+
32+
Verification
33+
A) Syntax/parse
34+
- node --check tools/shared/toolHostRuntime.js: PASS
35+
- node --check tools/Workspace Manager/main.js: PASS
36+
37+
B) Signature and call path checks
38+
- toolHostRuntime has launch signature:
39+
function launch(toolId, payloadJson, paletteJson = null): PASS
40+
- toolHostRuntime no mountTool API remains: PASS
41+
- Workspace Manager calls runtime.launch(toolId, payloadJson, paletteJson): PASS
42+
43+
C) Explicit-only guards
44+
- Runtime payloadJson object guard present: PASS
45+
- Runtime optional paletteJson object guard present: PASS
46+
- Workspace explicit payload block when absent present: PASS
47+
- readForwardedToolLaunchParams function removed: PASS
48+
49+
D) Ambiguity check
50+
- Implicit launch params forwarding removed from active launch path: PASS
51+
- Launch inputs are explicit-only at routing boundary: PASS
52+
53+
Result
54+
- PASS
55+
- No remaining launch-input ambiguity in the updated routing path.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# BUILD_PR_LEVEL_11_132_FINAL_TOOL_INPUT_ENFORCEMENT
2+
3+
## Purpose
4+
Lock the final rule: every tool receives ONLY its JSON payload (+ optional palette as separate JSON), with zero ambiguity.
5+
6+
## STRICT SCOPE
7+
8+
ALLOWED FILES:
9+
- routing files
10+
- tool launch handlers
11+
12+
ALLOWED CHANGES:
13+
- remove ANY remaining multi-input ambiguity
14+
- enforce explicit parameter passing
15+
16+
## FINAL CONTRACT
17+
18+
Tool launch MUST be:
19+
20+
launch(toolId, payloadJson, paletteJson?)
21+
22+
NOT:
23+
- launch(toolId, anythingElse)
24+
- launch(toolId, wrapper)
25+
- launch(toolId, parentJson)
26+
27+
## REQUIRED CHANGES
28+
29+
1. Ensure function signatures reflect:
30+
payload + optional palette
31+
32+
2. Remove:
33+
- implicit context passing
34+
- global/shared state reads
35+
- indirect lookup
36+
37+
3. Ensure:
38+
- payloadJson is exact file content
39+
- paletteJson is exact file content
40+
41+
## VALIDATION
42+
43+
- trace launch call
44+
- confirm ONLY payload + palette passed
45+
46+
## REPORT
47+
48+
docs/dev/reports/final_tool_input_contract_11_132.txt:
49+
- tools checked
50+
- call signatures
51+
- violations fixed
52+
53+
## FAILURE
54+
55+
FAIL if:
56+
- any tool still reads from external/global state
57+
- any implicit input exists

tools/Workspace Manager/main.js

Lines changed: 68 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -697,16 +697,21 @@ async function readWorkspaceManifestToolDiagnosticsFromSamplePreset(samplePreset
697697
reason: `fetch-failed(status=${response.status})`
698698
}
699699
],
700-
schemaContractError: schemaContract?.schemaContractError || ""
700+
schemaContractError: schemaContract?.schemaContractError || "",
701+
explicitToolPayloadById: new Map(),
702+
explicitPalettePayload: null
701703
};
702704
}
703705
const rawSource = await response.json();
704706
const classifications = classifyWorkspaceManifestTools(rawSource, schemaContract);
707+
const explicitInputs = extractWorkspaceManifestExplicitLaunchInputs(rawSource);
705708
return {
706709
sourcePath: normalizedPath,
707710
...classifications,
708711
visibleToolIds: [],
709-
schemaContractError: schemaContract?.schemaContractError || ""
712+
schemaContractError: schemaContract?.schemaContractError || "",
713+
explicitToolPayloadById: explicitInputs.explicitToolPayloadById,
714+
explicitPalettePayload: explicitInputs.explicitPalettePayload
710715
};
711716
} catch (error) {
712717
return {
@@ -723,9 +728,49 @@ async function readWorkspaceManifestToolDiagnosticsFromSamplePreset(samplePreset
723728
key: "",
724729
reason: `fetch-error(${error instanceof Error ? error.message : "unknown"})`
725730
}
726-
]
731+
],
732+
explicitToolPayloadById: new Map(),
733+
explicitPalettePayload: null
734+
};
735+
}
736+
}
737+
738+
function extractWorkspaceManifestExplicitLaunchInputs(rawSource) {
739+
const explicitToolPayloadById = new Map();
740+
let explicitPalettePayload = null;
741+
if (!isWorkspaceManifestSource(rawSource)) {
742+
return {
743+
explicitToolPayloadById,
744+
explicitPalettePayload
745+
};
746+
}
747+
const toolsBlock = rawSource.tools && typeof rawSource.tools === "object" && !Array.isArray(rawSource.tools)
748+
? rawSource.tools
749+
: null;
750+
if (!toolsBlock) {
751+
return {
752+
explicitToolPayloadById,
753+
explicitPalettePayload
727754
};
728755
}
756+
757+
Object.entries(toolsBlock).forEach(([rawToolKey, rawToolPayload]) => {
758+
const toolId = normalizeTextParam(rawToolKey).toLowerCase();
759+
if (!toolId || !rawToolPayload || typeof rawToolPayload !== "object" || Array.isArray(rawToolPayload)) {
760+
return;
761+
}
762+
explicitToolPayloadById.set(toolId, rawToolPayload);
763+
if (toolId === "palette-browser"
764+
&& rawToolPayload.schema === "html-js-gaming.palette"
765+
&& Array.isArray(rawToolPayload.swatches)) {
766+
explicitPalettePayload = rawToolPayload;
767+
}
768+
});
769+
770+
return {
771+
explicitToolPayloadById,
772+
explicitPalettePayload
773+
};
729774
}
730775

731776
function logWorkspaceManifestToolDiagnostics(diagnostics) {
@@ -746,48 +791,6 @@ function logWorkspaceManifestToolDiagnostics(diagnostics) {
746791
}
747792
}
748793

749-
function readForwardedToolLaunchParams() {
750-
const searchParams = new URL(window.location.href).searchParams;
751-
const forwarded = {};
752-
const sampleId = normalizeTextParam(searchParams.get("sampleId"));
753-
const sampleTitle = normalizeTextParam(searchParams.get("sampleTitle"));
754-
const samplePresetPath = normalizeLocalHrefParam(
755-
searchParams.get("samplePresetPath"),
756-
TOOL_LAUNCH_PARAM_PREFIXES.samplePresetPath
757-
);
758-
const gameId = normalizeTextParam(searchParams.get("gameId"));
759-
const gameTitle = normalizeTextParam(searchParams.get("gameTitle"));
760-
const gameHref = normalizeLocalHrefParam(searchParams.get("gameHref"), TOOL_LAUNCH_PARAM_PREFIXES.gameHref);
761-
const workspaceHref = normalizeLocalHrefParam(searchParams.get("workspaceHref"), TOOL_LAUNCH_PARAM_PREFIXES.workspaceHref);
762-
const returnTo = normalizeLocalHrefParam(searchParams.get("returnTo"), TOOL_LAUNCH_PARAM_PREFIXES.returnTo);
763-
764-
if (sampleId) {
765-
forwarded.sampleId = sampleId;
766-
}
767-
if (sampleTitle) {
768-
forwarded.sampleTitle = sampleTitle;
769-
}
770-
if (samplePresetPath) {
771-
forwarded.samplePresetPath = samplePresetPath;
772-
}
773-
if (gameId) {
774-
forwarded.gameId = gameId;
775-
}
776-
if (gameTitle) {
777-
forwarded.gameTitle = gameTitle;
778-
}
779-
if (gameHref) {
780-
forwarded.gameHref = gameHref;
781-
}
782-
if (workspaceHref) {
783-
forwarded.workspaceHref = workspaceHref;
784-
}
785-
if (returnTo) {
786-
forwarded.returnTo = returnTo;
787-
}
788-
return forwarded;
789-
}
790-
791794
function readSelectedToolId() {
792795
return selectedToolId;
793796
}
@@ -1225,38 +1228,29 @@ function mountSelectedTool(source = "manual") {
12251228
);
12261229
return false;
12271230
}
1228-
let optionalState = null;
1229-
if (refs.stateInput instanceof HTMLTextAreaElement) {
1230-
const rawState = refs.stateInput.value.trim();
1231-
if (rawState) {
1232-
try {
1233-
optionalState = JSON.parse(rawState);
1234-
} catch {
1235-
writeStatus("State JSON is invalid. Fix JSON or clear the state field.");
1236-
renderMountDiagnostic(
1237-
"Tool mount blocked by invalid state JSON.",
1238-
"Clear optional state or provide valid JSON before mounting."
1239-
);
1240-
return false;
1241-
}
1242-
}
1243-
}
1231+
const hasStateInput = refs.stateInput instanceof HTMLTextAreaElement
1232+
&& Boolean(refs.stateInput.value.trim());
12441233
updateSwitchMeta();
12451234
updateStandaloneHref(toolId);
12461235
writeQueryToolId(toolId, source === "init");
1247-
const previousMount = runtime.getCurrentMount();
1248-
const launchParams = readForwardedToolLaunchParams();
1249-
const mountResult = runtime.mountTool(toolId, {
1250-
source,
1251-
requestedAt: new Date().toISOString(),
1252-
sharedContext: {
1253-
requestedToolId: toolId,
1254-
previousToolId: previousMount?.tool?.id || "",
1255-
switchPosition: `${Math.max(1, getSelectedToolIndex() + 1)}/${Math.max(1, toolIds.length)}`
1256-
},
1257-
state: optionalState,
1258-
launchParams
1259-
});
1236+
const explicitToolPayloadById = workspaceManifestToolDiagnostics?.explicitToolPayloadById instanceof Map
1237+
? workspaceManifestToolDiagnostics.explicitToolPayloadById
1238+
: null;
1239+
const payloadJson = explicitToolPayloadById ? (explicitToolPayloadById.get(toolId) || null) : null;
1240+
if (!payloadJson || typeof payloadJson !== "object" || Array.isArray(payloadJson)) {
1241+
writeStatus(`Launch blocked for ${toolId}: explicit payloadJson is required.`);
1242+
renderMountDiagnostic(
1243+
`Launch blocked for ${toolId}.`,
1244+
"Workspace Manager now enforces explicit launch(toolId, payloadJson, paletteJson?) inputs."
1245+
);
1246+
syncControlState();
1247+
return false;
1248+
}
1249+
if (hasStateInput) {
1250+
writeStatus("State JSON is ignored for explicit launch signature enforcement.");
1251+
}
1252+
const paletteJson = workspaceManifestToolDiagnostics?.explicitPalettePayload || null;
1253+
const mountResult = runtime.launch(toolId, payloadJson, paletteJson);
12601254
if (!mountResult || !(mountResult.frame instanceof HTMLIFrameElement)) {
12611255
const selectedEntry = getToolHostEntryById(manifest, toolId);
12621256
const displayName = selectedEntry ? selectedEntry.displayName : toolId;

0 commit comments

Comments
 (0)