diff --git a/apps/storycore-harbour/README.md b/apps/storycore-harbour/README.md index 87b67679..dfbc0ce4 100644 --- a/apps/storycore-harbour/README.md +++ b/apps/storycore-harbour/README.md @@ -67,6 +67,12 @@ npm run dev:mock `npm install` also generates the bundle copy of the canonical acceptance corpus. `npm run check` runs the tests, contract, mock response, acceptance corpus/synchronization, and strict Anna validator. +With the mock Anna harness already running, set `BROWSER_EXECUTABLE` to Edge or +another compatible Chromium binary and run `npm run browser:check`. This runs +the complete generation/export smoke and the storage-deletion smoke +sequentially. Do not run them in parallel against one mock harness because the +fixture stream is shared. + For the real-model protocol, follow `acceptance/README.md`. Do not run the collector without an authenticated Anna test account, an enabled model, sufficient quota, and explicit confirmation in its UI. ## Release identity diff --git a/apps/storycore-harbour/STATUS.md b/apps/storycore-harbour/STATUS.md index aff223d7..c8444c35 100644 --- a/apps/storycore-harbour/STATUS.md +++ b/apps/storycore-harbour/STATUS.md @@ -288,4 +288,32 @@ Codex must inspect the latest CI and Sonar results first. The next legitimate im - The Anna adapter now exposes an optional user-entered model preference. Blank or invalid values preserve the Anna default; valid hints are advisory and only match models already enabled for that user. Normal StoryCore data and provider credentials remain unaffected. Automated gates pass: 65/65 Node tests, strict validation, Edge end-to-end smoke, and Edge deletion/storage-preservation smoke. - The complete fixed corpus with user preference `gemma` finished at 14/20: median 21.67 seconds, p95 39.78 seconds, 6 repaired passes, and no JSON truncation. Failures were A02 `reference_invalid`, A06/A09/A11 `contract_invalid`, A10 `warning_severity_invalid`, and A19 `required_field_invalid`. - Exact private-output replay identified two bounded schema aliases: A02 used warning `sceneId: "null"`; A10 used severity `minor`. Both now normalize to canonical `null` and `info`, and their exact real outputs pass the validator locally. This projects Gemma to 16/20 but does not replace the measured 14/20 score. Three malformed JSON responses and one structurally empty project remain rejected. +- A loopback-only cross-model experiment kept Anna default for primary generation and hinted Gemma only for the single repair. It passed A02/A15 directly but failed A07 (`contract_invalid`) and A18 (`unknown`): 2/4, worse than Gemma-only 3/4. The uncommitted experiment was removed; production retains one user-selected preference for both calls. +- A fresh Gemma A06 reproduction ruled out a bounded syntax-only repair. The primary output was missing one final brace, but appending it still left characters, locations, scenes, score, and warnings absent. The model repair was valid JSON yet again omitted the production bible and all core arrays. Parser recovery must remain fail-closed because required creative structure cannot be inferred safely. +- A single A06 diagnostic using Anna's documented example hint `gpt-4o` produced no completion or model metadata. The UI recorded timeout while the harness request remained pending for more than six minutes and only window heartbeats continued. The server was stopped to terminate the orphaned request; do not retry this hint until Anna exposes model grants or fixes cancellation/deadline propagation. - Reliability evidence is now a two-profile matrix: Anna default 16/20; Gemma preference 14/20. Both fail the 18/20 gate. No readiness, version cut, review, or release claim is permitted. +- An owner-authorized complete corpus rerun on 2026-08-27 used Anna default `minimax/minimax-m3` through OpenRouter and regressed to 6/20. Median successful duration was 49.13 seconds, p95 was 57.87 seconds, and 2 successful projects used repair. Privacy-safe failures were eight `json_invalid`, five `contract_invalid`, and one timeout. The complete private result remains ignored at `acceptance/results.2026-08-27.local.jsonl`. This stochastic regression does not justify weakening the contract or cutting a version; it reinforces the unresolved default-model reliability blocker. +- A second owner-authorized complete corpus rerun on 2026-08-27 used the advisory `gemma` hint and finished at 15/20. Median successful duration was 21.91 seconds, p95 was 41.92 seconds, and 6 successful projects used repair. Privacy-safe failures were A07/A10/A12/A19 `contract_invalid` and A20 `required_field_invalid`; there were no timeouts or JSON truncations. The private result remains ignored at `acceptance/results.2026-08-27.gemma.local.jsonl`. This improves the measured Gemma profile from 14/20 to 15/20 but remains below the 18/20 gate, so no readiness, version cut, review submission, or release claim is permitted. +- Future acceptance runs now classify schema, timestamp, duplicate, ordering, continuity-score, structure, and parent-scene reference failures with stable privacy-safe names. Unknown validation details still collapse to `contract_invalid`; generated content and private identifiers are never persisted in the public result. +- The privacy-safe diagnostic update is synchronized to Anna working draft revision 10. The 15-file, 104,031-byte bundle is ready with content hash `7d3edf2a89b942055c4af9d53b7dcfb6de1777e0437b27711b05bef09e2049b9`. The App remains a mutable draft with zero immutable versions; this synchronization is not an installation, review, or release claim. +- Read-only `apps grants storycore-harbour --json` currently returns `grants: null`. In CLI 0.1.30 this specifically represents a 404 from the informational grants endpoint (`no grants endpoint data available`), not evidence that the declared Host APIs were denied. Prior authenticated Host calls remain the capability evidence; do not use this null result to claim either a grant or a denial. +- Fresh Edge verification on 2026-08-28 passed the 520 × 680 generation/export flow, keyboard navigation, focus management, reduced-motion context, 400% text reflow, project reset/restore, and contract-valid export. The deletion smoke also passed sequentially with two ETag deletions, paginated listing, unrelated-key preservation, and a not-found reload. A new `browser:check` script fixes the intended sequential order because both smokes share one mock fixture stream. +- Edge now emulates `forced-colors: active` during the browser smoke and verifies visible keyboard focus, a non-colour selected-step outline, fully opaque dashed disabled controls, and a solid error boundary. The App also supplies forced-colour treatments for warning/success/error deletion states, armed destructive actions, and the continuity score. This automated evidence reduces risk but does not replace the remaining human Windows High Contrast and screen-reader passes. +- The forced-colour fix is synchronized to Anna working draft revision 11. Its 15-file, 104,890-byte bundle is ready with content hash `c5ddb6b3a45b42cebb3ceed713f65121ba3efac9808fece05aa05ad6ce863a17`; the App still has zero immutable versions and remains unpublished. +- Revision 11 demo evidence was regenerated locally in under twenty seconds: four visually inspected 900 × 820 PNGs plus a 3,288-byte contract-valid JSON export. The generated files remain ignored under `demo/output.local/revision-11/`; their sizes and one-run SHA-256 values are recorded in `demo/EVIDENCE_2026-08-28_REVISION_11.md`. The screenshot helper now resets every App scroll container before capture, fixing the measured missing-header defect on the World draft. +- Anna's public Developer Hub and pinned CLI schema were rechecked on 2026-08-29. They identify the Developer Console Listing tab as the metadata/media surface but expose no numeric screenshot or Marketplace-logo limits. Both available browser sessions required a fresh owner sign-in, so no authenticated field hints were claimed. `review/ANNA_MEDIA_REQUIREMENTS_REQUEST.md` contains the exact ready-to-send question; the 900 × 820 PNGs remain drafts and nothing was uploaded or submitted. +- Concurrent owner work on 2026-08-29 made the single repair prompt schema-complete: it now enumerates every required project, bible, entity, scene, shot, and continuity field; fixes the three-scene/one-shot shape; preserves exact source input; and still discards the failed response. The changes were merged without rewriting history and pass 67/67 Node tests, strict validation, the sequential Edge generation/export smoke, forced-colour assertions, and the ETag deletion smoke. +- The schema-complete repair prompt is synchronized to Anna working draft revision 12. Its 15-file, 107,699-byte bundle is ready with content hash `ad383957f123c1a2ea6c20e6ebb499f192f9ad161af4b0a9b0cc2bdb8e353fad`; the App remains an unpublished draft with zero immutable versions. No real-model run or quota consumption was performed for this synchronization. +- Authenticated Listing inspection on 2026-08-29 confirmed logo formats PNG/JPG/WebP/GIF, a 2MB maximum, and 256 × 256 cropping. Screenshot metadata remains one URL per line with no visible or HTML-enforced count, dimensions, aspect ratio, format, or byte limit. No field was changed and no asset was uploaded. +- A reproducible Marketplace logo candidate now renders from the committed SVG through Edge at `review/marketplace-media/storycore-harbour-logo-256.png`. It is 256 × 256, 5,302 bytes, SHA-256 `4a54e1955ac5ebe67eef8c82a260b1b1cdc351a7e87902f2bba2b15532cea7dd`, and passes PNG header, size, dimension, and representative-pixel validation. The first renderer attempt was correctly rejected after visual inspection exposed an unloaded black image; the renderer now embeds and decodes the SVG before capture. +- Authenticated Console inspection on 2026-08-29 confirmed working draft r12, bundle `ready`, content hash prefix `ad383957f123…`, and no version history. CLI status independently confirms `draft`, unpublished, and zero versions. The current Versions tab now exposes a distinct **Install & test** working-draft action, so an immutable cut is no longer assumed to be the only test path. It was not clicked. **View manifest** returned `Could not validate credentials`; this is recorded as a web-session/platform blocker rather than a bundle failure. +- A single instrumented **View manifest** reproduction produced the same credential message but no observable network event, HTTP status, or console error through the available browser diagnostics. Root cause remains unproven. `review/ANNA_VIEW_MANIFEST_CREDENTIAL_REPORT.md` contains privacy-safe steps, independent CLI evidence, the exact uncertainty boundary, and a ready-to-send support question; nothing was posted. +- After a fresh owner sign-in, **View manifest** succeeded and displayed the normalized working r12 manifest. It matches the committed Schema 2 Host-API-only boundary: no Executas, no top-level permissions, no external origins, one desktop view, `llm.complete`, App storage get/set/list/delete, `window.set_title`, self-only script CSP, and `last_writer_wins`. The credential incident is operationally resolved by reauthentication, while the exact token/frontend root cause remains unproven. No support message was sent. +- Installed Apps already contains StoryCore Harbour as `v0.0.0-dev`, so **Install & test** was not clicked again. Its read-only Permissions panel returns `Failed to load permissions: App version not found`. CLI status simultaneously confirms draft r12 has zero immutable versions and the grants endpoint exposes no data. The facts identify a dev-install/version-resolution blocker for permission management; they do not prove whether refresh, immutable cut, or server repair is the correct fix. `review/ANNA_DEV_INSTALL_PERMISSIONS_REPORT.md` contains the bounded support question, and nothing was sent or changed. +- Public Anna guidance rechecked on 2026-08-30 is internally ambiguous for this exact boundary: one guide moves installation/permissions after an immutable cut, another team response directs working-draft installation, and beta.126 excludes `0.0.0-draft` projections from release-candidate selection. The evidence is recorded in the permissions report and does not authorize a speculative reinstall or `0.1.0` cut. +- An owner-authorized complete corpus on 2026-08-31 used the advisory `gemma` hint after the schema-complete repair change. The connected Anna UI finished at 15/20, median 23.90 seconds, p95 44.00 seconds, and 7 repaired passes. Failures were HBR-A01 `unknown` after one transport `fetch failed`, plus HBR-A06/A08/A15/A20 `json_invalid`. Every malformed primary/repair response used `gemma-4-E4B-it` through Runpod, ended with `endTurn`, stayed between 1,269 and 1,722 output tokens, and ended with a closing brace; the remaining failures are internal JSON syntax errors rather than 4,096-token truncation. The score remains below 18/20, so no readiness, immutable cut, review, merge, or release claim is permitted. +- The iframe sandbox did not surface its Blob download to Browser automation. The private result was therefore recovered from the same run's RPC log: exactly 15 `projects/current` writes were matched to the immutable corpus, each recovered project passed the canonical contract, UI-displayed successful durations were retained at 0.1-second precision, and failure durations came from the sequential RPC timestamps. The canonical evaluator independently reproduced 15/20 and the same five privacy-safe failures. Ignored evidence remains at `acceptance/results.2026-08-31.gemma.recovered.local.jsonl`, `acceptance/harness.2026-08-31.gemma.local.log`, and companion local logs; generated content was not committed or posted. +- A private offline punctuation-recovery experiment made no model calls and never changed generated words. A bounded search of at most three edits from `{ } [ ] , :` recovered A08 and A15 with two edits each, so the best projected score was only 17/20. A06 remained unparsable after 28,876 bounded candidates. Closing A20's unbalanced JSON made it parse but left five canonical contract errors. There is no single bounded syntax rule that reaches the release gate, so the experimental helper was removed and production parsing remains fail-closed. +- Structured-output research on 2026-09-01 found that Anna documents `json_object`/`json_schema` for Executa `sampling/createMessage`, not for StoryCore's direct iframe `anna.llm.complete`. The pinned CLI 0.1.30 has no structured-output implementation; a read-only inspection of npm CLI 0.1.49 found negotiation only in the sampling bridge and still no direct Host API field. An Executa migration would violate the Host-API-only MVP boundary, and a current Cloud Agent report shows `json_schema` failures even with fallback. `review/ANNA_STRUCTURED_OUTPUT_REQUEST.md` contains the exact support question and a one-prompt gate; no package was upgraded and no model call was made. +- A 2026-09-05 refresh found no direct-Host structured-output update. CLI 0.1.51 still confines `response_format` negotiation to Executa sampling. Its enhanced read-only `apps grants` command also returned only `grants: null` for StoryCore, without the new `satisfied`/`missing` fields, matching the unresolved `v0.0.0-dev` permission-version gap. The newer CLI was executed ephemerally and not added to the project; no quota, grant, draft, version, or installation state changed. +- With explicit owner approval, one combined plain-text support email was sent to `hi@anna.partners` on 2026-09-05. It asks how to repair the existing `v0.0.0-dev` permission/version mismatch and whether direct iframe `anna.llm.complete` supports negotiated `json_object`/`json_schema`. It includes App id 214, slug, revision, privacy-safe 15/20 evidence, and the non-action boundaries; it contains no attachment, raw generated response, private JSONL, credential, or token. Do not send a duplicate while awaiting Anna's reply. diff --git a/apps/storycore-harbour/bundle/acceptance-failure.js b/apps/storycore-harbour/bundle/acceptance-failure.js index 65cedd80..366e2547 100644 --- a/apps/storycore-harbour/bundle/acceptance-failure.js +++ b/apps/storycore-harbour/bundle/acceptance-failure.js @@ -20,12 +20,27 @@ export function publicFailureName(message, category) { return "json_invalid"; } if (value.includes("severity must be")) return "warning_severity_invalid"; - if (value.includes("references unknown") || value.includes("unknown scene") || value.includes("unknown character")) { + if ( + value.includes("references unknown") || + value.includes("unknown scene") || + value.includes("unknown character") || + value.includes("not listed in the parent scene") + ) { return "reference_invalid"; } if (value.includes("duration")) return "duration_invalid"; + if (value.includes("schemaversion must be") || value.includes("project.format is unsupported")) { + return "schema_invalid"; + } + if (value.includes("iso-compatible date-time")) return "timestamp_invalid"; + if (value.includes("duplicate")) return "duplicate_invalid"; + if (value.includes("must be a positive integer")) return "ordering_invalid"; + if (value.includes("continuityreport.score")) return "continuity_score_invalid"; if (value.includes("required") || value.includes("must contain") || value.includes("must be a string")) { return "required_field_invalid"; } + if (value.includes("must be an array") || value.includes("must be an object")) { + return "structure_invalid"; + } return "contract_invalid"; } diff --git a/apps/storycore-harbour/bundle/repair-prompt.js b/apps/storycore-harbour/bundle/repair-prompt.js index 3f55ed8b..24664dec 100644 --- a/apps/storycore-harbour/bundle/repair-prompt.js +++ b/apps/storycore-harbour/bundle/repair-prompt.js @@ -1,10 +1,91 @@ +const REQUIRED_SHAPE = Object.freeze({ + schemaVersion: "storycore-harbour.project.v1", + project: [ + "id", + "title", + "language", + "format", + "durationMinutes", + "audience", + "tone", + "sourceIdea", + "createdAt", + "updatedAt", + ], + productionBible: [ + "logline", + "synopsis", + "themes[]", + "visualDirection.style", + "visualDirection.palette[]", + "visualDirection.lighting", + "visualDirection.cameraLanguage", + "continuityRules[]", + ], + character: [ + "id", + "name", + "role", + "goal", + "conflict", + "visualIdentity", + "continuityRules[]", + ], + location: [ + "id", + "name", + "purpose", + "visualIdentity", + "continuityRules[]", + ], + scene: [ + "id", + "order", + "title", + "purpose", + "locationId", + "characterIds[]", + "durationSeconds", + "shots[]", + ], + shot: [ + "id", + "order", + "framing", + "camera", + "action", + "dialogue", + "sound", + "characterIds[]", + "generationPrompt", + ], + continuityReport: ["score", "warnings[]", "checkedAt"], + warning: ["severity", "message", "sceneId"], +}); + export function createRepairPrompt(input, errors) { return JSON.stringify({ - task: "Rebuild a complete StoryCore Harbour production package from the source input.", + task: "Rebuild the complete StoryCore Harbour project from the source input. The previous answer failed validation, so return a fresh self-contained project rather than a patch.", input, validationErrors: errors, - constraints: { - jsonOnly: true, + requiredShape: REQUIRED_SHAPE, + hardRules: [ + "Return exactly one JSON object and nothing else.", + "Include every required field listed in requiredShape, even when a string is intentionally empty.", + "Create exactly 3 scenes and exactly 1 shot inside each scene.", + "Create 1-3 characters and 1-3 locations; every scene locationId must reference a declared location.", + "Every scene characterIds entry must reference a declared character.", + "Every shot characterIds entry must reference a declared character also listed in its parent scene.", + "Use unique ids, scene orders 1,2,3, and shot order 1 in every scene.", + "dialogue and sound are always strings; use an empty string when there is intentionally none.", + "themes, palette, continuityRules, characterIds, shots, and warnings are always arrays.", + "warning severity is only info, warning, or error; sceneId is a declared scene id or null.", + "continuityReport.score is a number from 0 through 100.", + "Preserve input.title exactly when it is non-empty, and preserve input language, format, duration, audience, tone, and source idea.", + "Keep the entire JSON below 12000 characters; do not omit required structure to save space.", + "Before returning, silently verify the project contains project, productionBible, characters, locations, scenes, and continuityReport.", + ], + sizeBudget: { maxCharacters: 12_000, scenes: 3, shotsPerScene: 1, @@ -13,7 +94,10 @@ export function createRepairPrompt(input, errors) { synopsisMaxWords: 80, descriptionMaxWords: 40, generationPromptMaxWords: 60, - discardPreviousResponse: true, + maxThemes: 3, + maxProductionContinuityRules: 3, + maxEntityContinuityRules: 2, }, + discardPreviousResponse: true, }); } diff --git a/apps/storycore-harbour/bundle/style.css b/apps/storycore-harbour/bundle/style.css index 4438a806..1d7cb26e 100644 --- a/apps/storycore-harbour/bundle/style.css +++ b/apps/storycore-harbour/bundle/style.css @@ -244,3 +244,32 @@ footer { @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; } } +@media (forced-colors: active) { + button:focus, input:focus, select:focus, textarea:focus { + outline: 3px solid Highlight; + outline-offset: 2px; + } + button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { + outline-color: Highlight; + } + .step.active { + color: HighlightText; + background: Highlight; + outline: 2px solid Highlight; + outline-offset: -3px; + } + button:disabled { + opacity: 1; + color: GrayText; + border-color: GrayText; + border-style: dashed; + } + .message.error, .error-panel, .deletion-status.error { + color: CanvasText; + border: 2px solid CanvasText; + } + .deletion-status.warning { border-style: dashed; } + .deletion-status.success { border-style: double; } + button.danger-outline.armed { outline: 3px double CanvasText; } + .score { border-color: CanvasText; } +} diff --git a/apps/storycore-harbour/demo/EVIDENCE_2026-08-28_REVISION_11.md b/apps/storycore-harbour/demo/EVIDENCE_2026-08-28_REVISION_11.md new file mode 100644 index 00000000..55fd5b7f --- /dev/null +++ b/apps/storycore-harbour/demo/EVIDENCE_2026-08-28_REVISION_11.md @@ -0,0 +1,24 @@ +# StoryCore Harbour demo evidence — Anna draft revision 11 + +Generated locally on 2026-08-28 with the deterministic Anna mock harness and +Microsoft Edge. No Anna model quota, production storage, provider key, or +private prompt was used. The local run completed in under twenty seconds. + +| Artifact | Dimensions | Bytes | SHA-256 | +| --- | ---: | ---: | --- | +| `01-concept.png` | 900 x 820 | 100,272 | `4e5f6ee1f694d30ba8639db511e7b843b92a57228c06b17a44a56be6a60b1e9a` | +| `02-world.png` | 900 x 820 | 105,664 | `60da7be8ca9c56aa6e94f65a36e6b9f23ce0271ee8cc9dc272f571a6b489a4f0` | +| `03-scenes.png` | 900 x 820 | 93,409 | `73e3745867bc21c99701a6b334a7311a86ef7b7a3c10665434cff3c1271c6dc5` | +| `04-continuity.png` | 900 x 820 | 94,405 | `1add93ddd51cd37086b88f73592f1e7864006384f375a2f50f4ad82af823df7d` | +| `browser-smoke-story.storycore-harbour.json` | n/a | 3,288 | `fe10fa513a326fc26d71aea7ddd6a508e9d5f69d57fbf7696c3c4aa95f2a72e2` | + +The JSON export passed the canonical `storycore-harbour.project.v1` validator. +The four screenshots were visually inspected for clipping, inconsistent scroll +position, missing headers, misleading media claims, and obvious unreadable +content. They remain draft Marketplace evidence until Anna confirms its exact +image dimensions and file-size rules. + +The generated artifacts remain intentionally ignored under +`demo/output.local/revision-11/`. Reproduce them with the exact procedure in +`demo/README.md`; do not commit generated project text or screenshots merely to +replace the authenticated real-model, accessibility, or beta gates. diff --git a/apps/storycore-harbour/demo/README.md b/apps/storycore-harbour/demo/README.md index 5bfd10e2..8fda9c0c 100644 --- a/apps/storycore-harbour/demo/README.md +++ b/apps/storycore-harbour/demo/README.md @@ -28,21 +28,23 @@ Keep that terminal open. In a second PowerShell terminal, from the same director ```powershell $env:BROWSER_EXECUTABLE = 'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe' $env:HARBOUR_URL = 'http://127.0.0.1:5180/' -$env:HARBOUR_SCREENSHOT_DIR = (Resolve-Path '.').Path + '\demo\output.local' -npm run browser:smoke +$env:HARBOUR_SCREENSHOT_DIR = (Resolve-Path '.').Path + '\demo\output.local\current' +npm run browser:check ``` Expected final line: ```text -{"result":"pass",...,"exportContract":"valid",...,"screenshotsCaptured":4,...} +{"result":"pass",...,"forcedColors":"pass",...,"exportContract":"valid",...,"screenshotsCaptured":4,...} +{"result":"pass","deletedProjectRecords":2,...,"unrelatedKeyPreserved":true,...} ``` Stop the mock harness with `Ctrl+C` after the browser command finishes. ## Produced evidence -The browser run writes these local, ignored artifacts to `demo/output.local/`: +The browser run writes these local, ignored artifacts to the selected +`HARBOUR_SCREENSHOT_DIR`: - `01-concept.png`; - `02-world.png`; @@ -50,7 +52,13 @@ The browser run writes these local, ignored artifacts to `demo/output.local/`: - `04-continuity.png`; - `browser-smoke-story.storycore-harbour.json`. -The browser test validates the actual export blob against `storycore-harbour.project.v1` before writing it. It also proves form validation, save/read-back, keyboard step navigation, focus management, the declared 520 x 680 minimum viewport, and 400% text reflow. +Each PNG is captured at 900 x 820. The browser test resets the App scroll before +every capture and validates the actual export blob against +`storycore-harbour.project.v1` before writing it. It also proves form +validation, save/read-back, keyboard step navigation, focus management, +forced-colour states, the declared 520 x 680 minimum viewport, 400% text +reflow, and safe project deletion. Hashes may vary with the Edge build and font +renderer; use them as one-run evidence, not portable golden-image assertions. ## Presenter script diff --git a/apps/storycore-harbour/package.json b/apps/storycore-harbour/package.json index 1a8b13dd..a717772a 100644 --- a/apps/storycore-harbour/package.json +++ b/apps/storycore-harbour/package.json @@ -18,6 +18,9 @@ "test": "node --test tests/*.test.mjs", "browser:smoke": "node scripts/browser-smoke.mjs", "browser:deletion-smoke": "node scripts/browser-deletion-smoke.mjs", + "browser:check": "npm run browser:smoke && npm run browser:deletion-smoke", + "marketplace:logo": "node scripts/render-marketplace-logo.mjs", + "marketplace:logo:check": "node scripts/validate-marketplace-logo.mjs", "contract:check": "node scripts/validate-project.mjs examples/sample-project.json", "acceptance:sync": "node scripts/sync-acceptance-corpus.mjs", "acceptance:sync:check": "node scripts/check-bundled-corpus.mjs", diff --git a/apps/storycore-harbour/review/ANNA_DEV_INSTALL_PERMISSIONS_REPORT.md b/apps/storycore-harbour/review/ANNA_DEV_INSTALL_PERMISSIONS_REPORT.md new file mode 100644 index 00000000..a116ba23 --- /dev/null +++ b/apps/storycore-harbour/review/ANNA_DEV_INSTALL_PERMISSIONS_REPORT.md @@ -0,0 +1,104 @@ +# Anna working-draft installation permissions report + +Prepared on 2026-08-29. An owner-approved combined support email was sent to +`hi@anna.partners` on 2026-09-05 with this installation question and the direct +structured-output question. No attachment or generated private result was sent. + +## Observed state + +- Developer Console: StoryCore Harbour working draft r12, bundle `ready`, no + immutable version history. +- Installed Apps: StoryCore Harbour is already present as `v0.0.0-dev`. +- Installed Apps > StoryCore Harbour > Permissions reports: + +```text +Failed to load permissions: App version not found +``` + +- CLI status independently reports App id 214, `draft`, unpublished, + `latest_version: null`, and `version_count: 0`. +- CLI versions independently returns an empty list. +- CLI grants returns `grants: null`, which in the pinned CLI means the grants + endpoint supplied no data; it does not independently prove grant or denial. + +## Interpretation boundary + +The verified facts show that a development installation record exists while +the permission-management surface cannot resolve an App version. This explains +why StoryCore appears installed but its expected LLM/storage grants cannot yet +be inspected through Installed Apps. + +It is reasonable to suspect a platform working-draft/version-resolution gap, +but the evidence does not prove whether the fix is to refresh **Install & +test**, create an immutable version, or repair the existing dev-install record +server-side. Do not cut `0.1.0`, reinstall, uninstall, or change permissions as +a diagnostic shortcut. + +## Public guidance checked on 2026-08-30 + +Current Anna guidance is not unambiguous enough to choose a destructive or +immutable workaround: + +- the beginner publishing guide describes a working draft as testable, but its + permission walkthrough says to cut a version and then install it; +- an Anna Forum team response separately instructs developers to install a + working draft from Developer Console; +- the beta.126 changelog calls `0.0.0-draft` a projection row and explicitly + excludes it from release-candidate selection. + +Sources: + +- +- +- + +This conflict reinforces the support question below. It does not justify +cutting `0.1.0` while StoryCore's measured reliability gate remains below +18/20. + +## Safety boundary + +- **Install & test** was not clicked again because StoryCore is already listed. +- The permission dialog was read but no control was changed or saved. +- No version was cut, no review was submitted, no App was removed, and no model + or storage quota was consumed. + +## Ready-to-send support question + +Sent in the combined 2026-09-05 support email. Do not send a duplicate while a +reply is pending. + +```text +Hi Anna team — StoryCore Harbour (App id 214) has a ready working draft at r12 +and is already listed in Installed Apps as v0.0.0-dev. However, opening its +Permissions panel returns “Failed to load permissions: App version not found”. + +The Developer Console and pinned CLI both confirm that the App is a draft with +zero immutable versions. Could you confirm the intended permissions path for a +working-draft Install & test record? + +1. Should a v0.0.0-dev installation resolve permissions directly from the + current working draft? +2. Is Install & test expected to refresh an existing dev-install record? +3. Is an immutable cut required for permission management, despite the + working-draft Install & test action? +4. If this is a stale dev-install record, can it be repaired server-side + without recreating the App or losing the reserved slug? + +We have not reinstalled, uninstalled, changed grants, cut a version, submitted +review, or published anything. +``` + +## 2026-09-05 CLI 0.1.51 read-only check + +Anna CLI 0.1.51 adds a richer `apps grants` reader that first queries the App +permissions endpoint and normally reports `satisfied`, `missing`, and bundled +Executa grants. Running that command ephemerally against StoryCore Harbour still +returned only `grants: null`, with none of those permissions fields. This is +consistent with the Console's `App version not found` result: the improved CLI +cannot resolve permissions for the existing `v0.0.0-dev` record either. + +The CLI was not added to the project, no grant was changed, and the temporary +package inspection directory was removed. Version 0.1.51 does not provide a +grant mutation command; the dashboard remains the documented permission-change +surface. diff --git a/apps/storycore-harbour/review/ANNA_MEDIA_REQUIREMENTS_REQUEST.md b/apps/storycore-harbour/review/ANNA_MEDIA_REQUIREMENTS_REQUEST.md new file mode 100644 index 00000000..2431f2f6 --- /dev/null +++ b/apps/storycore-harbour/review/ANNA_MEDIA_REQUIREMENTS_REQUEST.md @@ -0,0 +1,61 @@ +# Anna Marketplace media requirements request + +Prepared on 2026-08-29. This is a support-message draft, not a submission or +authorization to upload files. + +## Verified context + +- Anna's public App Manifest documentation says listing metadata, logos, and + screenshots are managed in the Developer Console Listing tab. +- The authenticated Listing inspected on 2026-08-29 accepts PNG/JPG/WebP/GIF + logos up to 2MB and states that they are cropped to 256 x 256. +- Screenshots are entered as one URL per line. The Listing exposes no visible + screenshot count, dimension, aspect-ratio, format, or byte limit, and the + textarea has no corresponding HTML constraint attributes. +- StoryCore Harbour currently has four deterministic fictional PNG drafts at + 900 x 820. Their largest file is 105,664 bytes. +- The draft is App id 214, slug `storycore-harbour`, working revision 11, with + zero immutable versions and no public release. + +Official pages checked: + +- +- + +## Ready-to-send support message + +```text +Hi Anna team — we are preparing the first review package for StoryCore Harbour +(@storycore-labs/storycore-harbour), a Schema 2 Host-API-only App with no +Executa. + +Could you confirm the current Marketplace media requirements shown to reviewers +and developers? + +1. required screenshot count and accepted formats; +2. exact pixel dimensions or aspect-ratio range; +3. maximum bytes per screenshot and whether Anna fetches each URL at review or + requires a long-lived public asset URL; +4. any screenshot safe-area, rounded-corner, text-overlay, localization, or dark/light + theme requirements; +5. whether screenshots from the local Anna harness are acceptable when they + contain only fictional data and accurately represent the submitted bundle. + +The Listing already confirms that logos may be PNG/JPG/WebP/GIF up to 2MB and +are cropped to 256 x 256. Our prepared PNG logo is 256 x 256 and 5,302 bytes. + +Our current screenshot drafts are four PNG files at 900 x 820, each below 106 KB. They show +Concept, World/production bible, Scenes/shots, and Continuity/export. They do not +show account identifiers, hidden acceptance controls, provider metadata, or +real user content. + +We will not treat these drafts as final until the current requirements are +confirmed. Thank you. +``` + +## Owner action + +The authenticated Listing constraints above have been inspected. The owner may +send the remaining screenshot question through Anna's official support channel. Do not +upload assets, submit a review, accept terms, or make a public post without the +owner's explicit approval at the time of the action. diff --git a/apps/storycore-harbour/review/ANNA_STRUCTURED_OUTPUT_REQUEST.md b/apps/storycore-harbour/review/ANNA_STRUCTURED_OUTPUT_REQUEST.md new file mode 100644 index 00000000..ad12b3d7 --- /dev/null +++ b/apps/storycore-harbour/review/ANNA_STRUCTURED_OUTPUT_REQUEST.md @@ -0,0 +1,104 @@ +# Anna direct Host LLM structured-output request + +Prepared on 2026-09-01. An owner-approved combined support email was sent to +`hi@anna.partners` on 2026-09-05 with this question and the working-draft +permission issue. No attachment or generated private result was sent, and no +model call was made for this investigation. + +## StoryCore evidence + +- StoryCore Harbour is a Schema 2, Host-API-only App with no Executa. +- Its current generation path is `anna.llm.complete` from the iframe bundle. +- The 2026-08-31 Gemma corpus measured 15/20. Four final repair responses were + complete, non-truncated, and ended with `}`, but contained internal JSON + syntax errors. +- A punctuation-only offline search could recover only two cases, projecting + 17/20; it does not justify a permissive production parser. + +## Verified Anna surfaces + +Anna supports structured output on Executa reverse sampling: + +- `sampling/createMessage` accepts `responseFormat` with `json_object` or + `json_schema` plus `onUnsupported`; +- the real bridge sends the negotiated value as `response_format` to the + platform completion endpoint; +- CLI structured-output emulation exists for the sampling development path. + +This support is not currently documented or typed for the iframe Host API +`anna.llm.complete`. Its published signature lists messages, `maxTokens`, +`modelPreferences`, `systemPrompt`, temperature, stop sequences, and metadata, +but no response-format field. + +Local package inspection confirms the same boundary: + +- pinned CLI 0.1.30 contains no `responseFormat`, `response_format`, or + `onUnsupported` implementation; +- latest npm CLI 0.1.49 adds structured negotiation to its sampling bridge; +- CLI 0.1.49 still exposes no response-format field for direct Host + `llm.complete`. + +Moving StoryCore generation into an Executa only to obtain JSON Schema would +add a backend/distribution/permission boundary and violate the MVP's intended +Host-API-only architecture. Do not make that change without explicit product +and architecture approval. + +## Current platform caution + +The Anna Forum currently reports a Cloud Agent failure for `json_schema` even +with `onUnsupported=json_object`. Structured sampling is therefore not yet a +drop-in reliability proof for StoryCore. + +Sources checked: + +- +- +- +- + +## Ready-to-send question + +Sent in the combined 2026-09-05 support email. Do not send a duplicate while a +reply is pending. + +```text +Hi Anna team — StoryCore Harbour is a Schema 2 Host-API-only App using direct +iframe anna.llm.complete, with no Executa. Our latest fixed Gemma corpus is +15/20; four failed repairs are complete/non-truncated responses with internal +JSON syntax errors. + +The current sampling/createMessage path supports responseFormat +(json_object/json_schema) and onUnsupported, but the documented direct +anna.llm.complete signature and current CLI types do not expose those fields. + +1. Does direct iframe anna.llm.complete currently accept responseFormat or + response_format in production? +2. If yes, what exact wire shape and capability-negotiation response should a + Schema 2 App use? +3. Is json_object supported for gemma-4-E4B-it/Runpod through the App-complete + path? +4. Can unsupported structured output fail explicitly without silently falling + back to prompt-only text? +5. Is there a recommended Host-API-only example or minimum runtime/CLI version? + +We will not add an Executa, send another full corpus, or claim reliability from +an undocumented field. With confirmation, we can run one owner-authorized +single-prompt probe before considering any code change. +``` + +## Probe gate + +Do not add an undocumented field to production or consume quota until Anna +confirms the direct Host API contract. If confirmed, implement the smallest +adapter-only opt-in and run one previously failing prompt first. A complete +corpus requires separate owner quota approval after that pilot succeeds. + +## 2026-09-05 refresh + +No newer public Anna reference or forum answer was found that adds +`responseFormat` to direct iframe `anna.llm.complete`. Anna CLI 0.1.51 was +inspected without installation into the project. Its real sampling bridge +negotiates `responseFormat` and forwards `response_format`, but its direct App +LLM bridge still exposes no structured-output contract. Do not upgrade the +pinned CLI merely for this issue and do not send an undocumented field to the +production endpoint. diff --git a/apps/storycore-harbour/review/ANNA_VIEW_MANIFEST_CREDENTIAL_REPORT.md b/apps/storycore-harbour/review/ANNA_VIEW_MANIFEST_CREDENTIAL_REPORT.md new file mode 100644 index 00000000..2d74cefe --- /dev/null +++ b/apps/storycore-harbour/review/ANNA_VIEW_MANIFEST_CREDENTIAL_REPORT.md @@ -0,0 +1,105 @@ +# Anna Developer Console credential-report draft + +Prepared on 2026-08-29. This is a private support-report draft; it has not been +sent or posted. + +## Resolution update + +After the owner completed a fresh Anna sign-in and returned through the +Dashboard, **View manifest** succeeded in the same in-app browser. It displayed +working draft r12 and the normalized remote manifest. The earlier failure is +therefore resolved operationally by reauthentication, although the exact token +or frontend validation mechanism remains unproven. + +The remote manifest confirms: + +- schema 2; +- no required or optional Executas; +- no top-level permissions; +- no external bundle origins; +- one desktop view with minimum 520 x 680 and default 900 x 820; +- `llm.complete` only in the LLM namespace; +- App storage `get`, `set`, `list`, and `delete`; +- `window.set_title`; +- CSP `script-src 'self'` and `last_writer_wins` state merge. + +These boundaries match the committed Host-API-only adapter. The support message +below should now be sent only if the credential error recurs after a fresh +login; it is retained as a reproducible diagnostic template. + +## Summary + +The authenticated Anna Developer Console can list StoryCore Harbour and show +its working draft, but the read-only **View manifest** action reports: + +```text +Could not validate credentials +``` + +## Reproduction + +1. Sign in to Anna and complete workspace onboarding. +2. Open Developer Console. +3. Open StoryCore Harbour, App id 214. +4. Open **Versions**. +5. Confirm the working draft shows revision r12, bundle `ready`, and content + hash prefix `ad383957f123…`. +6. Click **View manifest**. +7. Observe `Could not validate credentials`; no manifest modal/content appears. + +## Independent evidence + +- `anna-app apps status storycore-harbour --json` succeeds with the same owner + account and reports `draft`, unpublished, zero versions. +- `anna-app apps versions storycore-harbour --json` succeeds and returns an + empty immutable-version list. +- Working draft revision 12 was uploaded with the pinned Node 22-compatible + CLI flow and bundle status `ready`. +- Local strict validation, canonical contract, mock fixture, fixed corpus, + browser flow, and deletion flow pass. +- The Console can read the App list, Listing, working revision, bundle status, + Settings, and version history in the same browser session. + +## Instrumentation result + +One instrumented reproduction was performed after enabling browser network +observation. The UI reproduced the same message, but the available event buffer +and console logs exposed no request URL or HTTP status. Do not claim whether +the failure occurs before the request, in an unobserved request, or in response +handling without server/frontend evidence. + +## Expected behavior + +**View manifest** should display the immutable snapshot of the current working +manifest, or return an actionable authentication/authorization error that +identifies which owner/developer credential must be refreshed. + +## Safety and impact + +- No manifest, Listing field, App permission, installation, or version was + changed during reproduction. +- **Install & test**, **Cut version**, **Submit now**, **Discard**, and deletion + actions were not used. +- This blocks a web-console manifest comparison and reduces confidence in the + new working-draft install path. It does not prove that the uploaded manifest + or bundle is invalid. + +## Ready-to-send question + +```text +Hi Anna team — the authenticated Developer Console lists StoryCore Harbour +(App id 214) and shows working draft r12 with bundle ready, but Versions > View +manifest returns “Could not validate credentials”. The pinned CLI can read the +same App status and versions successfully, and strict local validation passes. + +Could you confirm which web credential or endpoint View manifest requires, and +whether refreshing that credential is also required before using the new +working-draft “Install & test” action? We have not clicked Install & test, cut a +version, submitted review, or changed permissions. +``` + +## Closure boundary + +This resolution does not authorize **Install & test**, enable Host API grants, +cut a version, spend model quota, submit review, merge, or publish. It only +closes the read-only remote-manifest comparison gate. diff --git a/apps/storycore-harbour/review/FINAL_HANDOFF_2026-08-24.md b/apps/storycore-harbour/review/FINAL_HANDOFF_2026-08-24.md index fec6a12a..db0d6998 100644 --- a/apps/storycore-harbour/review/FINAL_HANDOFF_2026-08-24.md +++ b/apps/storycore-harbour/review/FINAL_HANDOFF_2026-08-24.md @@ -1,5 +1,7 @@ # Anna opportunity final handoff — 24 August 2026 +Updated with verified remote state on 28 August 2026. + This document records the verified draft state. It is not authorization to cut a version, submit a review, publish, accept new terms, or spend additional model quota. ## StoryCore Harbour @@ -7,19 +9,19 @@ This document records the verified draft state. It is not authorization to cut a ### Local candidate - branch: `codex/anna-mvp-20260824`; -- latest integration commit before this handoff refresh: `ec2d9da7`; -- GitHub branch `agent/storycore-harbour-bootstrap` was updated through PR #30 without force-push; the PR remains draft and unmerged; +- latest integration commit at this handoff refresh: `03c2c54b`; +- GitHub branch `agent/storycore-harbour-bootstrap` is synchronized through draft PR #37 without force-push; PR #30 and PR #36 are merged, while PR #37 remains open, green, and unmerged; - core StoryCore Engine checkout was not merged, reset, cleaned, or overwritten; - Anna adapter remains isolated under `apps/storycore-harbour/`; -- automated gate: 60/60 Node tests, sample contract, mock fixture, fixed corpus, bundle synchronization, and strict Anna validation pass. +- automated gate: 66/66 Node tests, sample contract, mock fixture, fixed corpus, bundle synchronization, strict Anna validation, GitHub Anna CI, and SonarQube pass. ### Anna draft - public identity: `@storycore-labs/storycore-harbour`; - server App id: `214`; -- working revision: `9`; -- content hash: `762e175e7f150d47845b3fa4ace51d2f058d1758cc4c19f06d621b9756dada74`; -- bundle: 15 files, 103,405 bytes, `ready`; +- working revision: `10`; +- content hash: `7d3edf2a89b942055c4af9d53b7dcfb6de1777e0437b27711b05bef09e2049b9`; +- bundle: 15 files, 104,031 bytes, `ready`; - status: `draft`, not published; - immutable versions: 0; - Executas/local shims: 0. @@ -99,7 +101,13 @@ without addressing the failure. ### Submission decision -Do not cut `0.1.0`, submit for review, mark PR #30 ready, merge, or release while the official gate remains below 18/20. The App is demonstrable and its working draft is reserved, but it is not submission-ready under the repository's own rules. +Do not cut `0.1.0`, submit for review, mark PR #37 ready, merge, or release while the official gate remains below 18/20. The latest measured Gemma run is 15/20 and the latest Anna-default run is 6/20. The App is demonstrable and its working draft is reserved, but it is not submission-ready under the repository's own rules. + +The owner-authorized 2026-08-31 Gemma rerun after the schema-complete repair +also measured 15/20 (median 23.90 seconds, p95 44.00 seconds, 7 repaired +passes). A01 failed at transport; A06/A08/A15/A20 returned complete but +syntactically invalid JSON. This supersedes the 27 August Gemma run as the +latest real evidence without changing the submission decision. ### Anna installation diagnosis @@ -109,6 +117,22 @@ Do not cut `0.1.0`, submit for review, mark PR #30 ready, merge, or release whil - cutting `0.1.0` would create that immutable version but must not be used as a workaround while the 18/20 reliability gate still fails; - the Console web session also expired on reload and redirected to login, which is a separate authentication condition rather than the original installation cause. +Update from 2026-08-29: the current Console now exposes a separate **Install & +test** button inside the working-draft Versions tab at revision 12, while CLI +status still reports zero immutable versions. This may provide a draft-testing +path without cutting `0.1.0`, but it was not clicked because installation and +permission prompts require explicit owner approval. **View manifest** returned +`Could not validate credentials`; therefore the web-session credential path +must be healthy before any installation result can be claimed. + +Second update from 2026-08-29: after reauthentication, **View manifest** works +and matches the committed Host-API-only boundaries. Installed Apps already +contains StoryCore Harbour as `v0.0.0-dev`, so another installation was not +attempted. Its Permissions panel fails with `App version not found`, while CLI +status still confirms zero immutable versions. The current blocker is therefore +permission resolution for the existing development installation, not absence +of an Installed Apps record. See `ANNA_DEV_INSTALL_PERMISSIONS_REPORT.md`. + ## AIMesher Anna App ### Local candidate diff --git a/apps/storycore-harbour/review/LAUNCH_CHECKLIST.md b/apps/storycore-harbour/review/LAUNCH_CHECKLIST.md index 5ffd0577..e2a97625 100644 --- a/apps/storycore-harbour/review/LAUNCH_CHECKLIST.md +++ b/apps/storycore-harbour/review/LAUNCH_CHECKLIST.md @@ -31,12 +31,17 @@ This checklist is a release gate. A checked implementation item does not overrid - [x] Manifest declares only LLM, App storage, and window-title Host APIs. - [x] Undeclared tool invocation is denied in the official test harness. - [x] Mock Anna harness starts in CI. +- [x] Authenticated Console View manifest matches the committed Schema 2 Host-API-only boundaries at r12. - [x] Browser flow runs inside the Anna harness at 520 × 680. - [ ] Production Host API handshake verified in the target Anna account. - [ ] Production App storage write/read/reload verified. - [ ] Production ETag conflict induced and safely rejected. - [ ] Qualified App MAU definition and dashboard visibility confirmed. - [ ] Host-API-only completion confirmed as eligible without local runtime installation. +- [ ] Owner-approved working-draft **Install & test** completes from revision 12 without cutting a version. +- [ ] Installed Apps exposes only the expected LLM, App storage, and window capabilities with no Executa. +- [x] Existing Installed Apps record identified as StoryCore Harbour `v0.0.0-dev`. +- [ ] Resolve `App version not found` when opening permissions for the existing dev installation. ## Reliability and CI diff --git a/apps/storycore-harbour/review/MVP_ROADMAP_2026-08-24.md b/apps/storycore-harbour/review/MVP_ROADMAP_2026-08-24.md index 3a571035..7c92f140 100644 --- a/apps/storycore-harbour/review/MVP_ROADMAP_2026-08-24.md +++ b/apps/storycore-harbour/review/MVP_ROADMAP_2026-08-24.md @@ -88,3 +88,11 @@ Do not replace production-platform gates with these local results. - User control complete: the Anna adapter offers an optional validated model hint and otherwise preserves the Anna default. It has 65-test, strict-validation, end-to-end Edge, and deletion/storage-preservation evidence. - Complete Gemma-preference corpus: 14/20, median 21.67 seconds, p95 39.78 seconds, 6 repaired passes. It is faster and avoids MiniMax truncation but creates six schema/reference failures. Keep this score separate from Anna-default 16/20; neither satisfies readiness. - Post-run exact replay: canonicalizing warning severity `minor→info` and string scene reference `"null"→null` makes the measured A02/A10 outputs valid, projecting 16/20 without weakening any structural rule. The measured score remains 14/20 until a real rerun; the four remaining outputs are genuinely malformed or structurally empty. +- Rejected experiment: Anna-default primary plus Gemma-only repair scored 2/4 on A02/A07/A15/A18, below Gemma-only 3/4. The diagnostic code was removed; do not add separate repair-model complexity without new platform evidence. +- A06 reproduction: one missing final brace was not the root cause. Both the completed primary object and the syntactically valid repair lacked the required creative structure. Keep fail-closed validation; do not synthesize characters, locations, scenes, or continuity data in the parser. +- Third-model probe blocked: hint `gpt-4o` returned no response metadata and remained pending beyond the App timeout. Treat it as a platform cancellation/grant issue, not model evidence; no further blind hint probes. +- 27 August default-model rerun: the complete immutable corpus used `minimax/minimax-m3` through OpenRouter and finished at 6/20, median 49.13 seconds, p95 57.87 seconds, and 2 repaired passes. The privacy-safe failure matrix was eight `json_invalid`, five `contract_invalid`, and one timeout. The ignored private JSONL is `acceptance/results.2026-08-27.local.jsonl`. This is a measured stochastic regression from the earlier 16/20 default run, so readiness remains blocked and no parser relaxation, immutable version, review submission, or release is justified. +- 27 August Gemma rerun: the complete immutable corpus with advisory hint `gemma` finished at 15/20, median 21.91 seconds, p95 41.92 seconds, and 6 repaired passes. Failures were A07/A10/A12/A19 `contract_invalid` and A20 `required_field_invalid`, with no timeout or JSON truncation. The ignored private JSONL is `acceptance/results.2026-08-27.gemma.local.jsonl`. This improves the measured Gemma profile from 14/20 to 15/20 but remains below the 18/20 gate; keep the PR draft and do not cut, submit, or release a version. +- 31 August post-repair Gemma rerun: the complete connected corpus again finished at 15/20, median 23.90 seconds, p95 44.00 seconds, and 7 repaired passes. A01 failed at transport; A06/A08/A15/A20 were `json_invalid` after complete, non-truncated Gemma/Runpod responses. The schema-complete repair changed which prompts pass and increased repaired passes, but did not improve the total score. The private result was canonically re-evaluated from an RPC-log recovery because the iframe download was not surfaced to automation. Keep the release gate failed and do not fund another blind rerun without a measured syntax intervention or platform/model change. +- Offline syntax experiment: a maximum-three-edit punctuation search recovered only A08/A15, projecting 17/20. A06 had no valid candidate; structurally closing A20 still left five contract failures. Do not add a general JSON-repair dependency or permissive parser from this evidence. Production remains fail-closed; the next useful evidence requires a model/platform change or a specifically measured generation constraint, not another blind corpus run. +- Structured-output boundary: Anna's JSON Schema support is currently verified for Executa sampling, not the direct iframe `anna.llm.complete` path used by StoryCore. CLI 0.1.49 still confines response-format negotiation to sampling, and current Cloud Agent reports show structured fallback failures. Keep StoryCore Host-API-only; ask Anna for the direct contract, then permit at most one failing-prompt pilot before any adapter change or full rerun. diff --git a/apps/storycore-harbour/review/OWNER_ACTIVATION_AND_FIRST_REVIEW.md b/apps/storycore-harbour/review/OWNER_ACTIVATION_AND_FIRST_REVIEW.md index de345d56..c51c890c 100644 --- a/apps/storycore-harbour/review/OWNER_ACTIVATION_AND_FIRST_REVIEW.md +++ b/apps/storycore-harbour/review/OWNER_ACTIVATION_AND_FIRST_REVIEW.md @@ -173,10 +173,18 @@ Only after checking the dry-run output: npx --no-install anna-app apps push --json npx --no-install anna-app apps status storycore-harbour --json npx --no-install anna-app apps list --json +npx --no-install anna-app apps grants storycore-harbour --json ``` `apps push` creates or updates a mutable **working draft**. It is not a public release and should not make the App visible in the public Store. +The grants endpoint is informational only. With the currently pinned CLI, a +JSON result containing `"grants": null` means the server returned 404 for the +public grants endpoint and the CLI has no endpoint data available. It does not +prove that the App was denied the Host APIs declared in the manifest. Confirm +actual LLM, storage, and window capabilities through the authenticated harness +and recorded Host calls; do not infer permission state from `null`. + After the first successful push: - verify that `.anna/app.json` identifies the expected remote App; @@ -184,6 +192,38 @@ After the first successful push: - open the Developer Console and confirm the App is shown as a working/draft App; - confirm the locked slug is exactly `storycore-harbour`. +### Working-draft installation path + +The authenticated Developer Console inspected on 2026-08-29 exposes an +**Install & test** action inside the **Versions** tab for the mutable working +draft, even while the App has zero immutable versions. This is distinct from +the App-list **Install** action that previously failed with “no available +published version”. Do not cut `0.1.0` merely to discover whether the current +working-draft installation path is usable. + +Installing changes the owner's Installed Apps state and may open Host API +permission controls. Use **Install & test** only after explicit owner approval +at action time, then verify the exact App id, working revision, requested +capabilities, and absence of unexpected Executas before confirming anything. + +During the same inspection, **View manifest** returned `Could not validate +credentials` although the Console displayed working revision 12 and the CLI +independently confirmed the draft. Treat that as a web-session/platform +credential condition, not as evidence that the uploaded manifest or bundle is +invalid. Reauthenticate or ask Anna support rather than recreating the App. + +A fresh owner sign-in subsequently restored **View manifest**. The normalized +remote r12 manifest matched the committed Schema 2 Host-API-only boundaries. +Keep `review/ANNA_VIEW_MANIFEST_CREDENTIAL_REPORT.md` as the recurrence report; +do not send it while the read path remains healthy. + +Installed Apps inspection then showed StoryCore Harbour already present as +`v0.0.0-dev`. Do not click **Install & test** again merely because the +Developer card remains at `v0.0.0`. The existing installation's Permissions +panel currently returns `App version not found`; stop there and use +`review/ANNA_DEV_INSTALL_PERMISSIONS_REPORT.md` rather than reinstalling, +uninstalling, cutting a version, or changing grants speculatively. + If the CLI or Console creates an unexpected second App, stop before cutting a version. Preserve the outputs needed for diagnosis, but redact tokens. ## 6. Run StoryCore Harbour against real Anna services diff --git a/apps/storycore-harbour/review/SONAR_SECURITY_FOLLOWUP_2026-08-30.md b/apps/storycore-harbour/review/SONAR_SECURITY_FOLLOWUP_2026-08-30.md new file mode 100644 index 00000000..183b0b72 --- /dev/null +++ b/apps/storycore-harbour/review/SONAR_SECURITY_FOLLOWUP_2026-08-30.md @@ -0,0 +1,79 @@ +# Sonar security follow-up — 2026-08-30 + +## Result + +The StoryCore Harbour security gate is restored. + +After the Marketplace-logo helper hardening, SonarQube Cloud completed a fresh analysis on PR #37 and reported: + +- **Quality Gate passed**; +- **0 New issues**; +- **0 Accepted issues**; +- **0 Security Hotspots**. + +This supersedes the earlier C Security Rating result and the intervening Automatic Analysis failures. No issue was suppressed or accepted and the Quality Gate was not lowered. + +## Context + +Draft PR #37 had previously passed the StoryCore Harbour functional CI while SonarQube Cloud reported a **C Security Rating on New Code** with two annotations. The regression appeared after the Marketplace-logo helper scripts were added. + +The two helper surfaces were then hardened. Several intervening Sonar Automatic Analysis attempts reported only `The last analysis has failed`; those attempts were treated as inconclusive rather than as evidence of recovery. + +## Bounded remediation + +The two new Marketplace-logo scripts were hardened without broadening App permissions or runtime capabilities. + +### Renderer + +`./scripts/render-marketplace-logo.mjs` + +- no longer accepts CLI-supplied input or output paths; +- derives the App root from `import.meta.url`; +- reads only committed `bundle/icon.svg`; +- writes only `review/marketplace-media/storycore-harbour-logo-256.png`; +- no longer interpolates the SVG data URL into an HTML template; +- assigns the fixed local source as the image `src` property; +- no longer prints local filesystem paths in its result log. + +### Validator + +`./scripts/validate-marketplace-logo.mjs` + +- no longer accepts a CLI-supplied logo path; +- reads only the committed StoryCore Harbour Marketplace PNG; +- checks PNG signature, IHDR, 256 × 256 dimensions, the 2 MB limit, and representative pixels; +- no longer prints the local filesystem path. + +### Regression coverage + +`tests/marketplace-logo.test.mjs` independently verifies that the committed asset: + +- is a PNG; +- begins with IHDR; +- is exactly 256 × 256; +- is non-empty; +- remains below 2 MB. + +StoryCore Harbour CI run #136 is green after the final evidence update. + +## Sonar service evidence during diagnosis + +Public Sonar Community reports dated 2026-08-28 and 2026-08-29 described contemporaneous SonarQube Cloud Automatic Analysis failures with the same broad symptom seen during the intervening attempts: ordinary CI remained green while Automatic Analysis reported `The last analysis has failed`, and one report described no Compute Engine task being created. + +Relevant public reports included: + +- `Automatic Analysis silently failing since 2026-08-28 — failing analysis ID provided` (2026-08-29); +- `SonarQube Cloud Automatic Analysis queues GitHub checks but starts no CE task for PR #8` (2026-08-28); +- `New repo, Error: The last analysis has failed` (2026-08-28). + +Those reports remain corroborating evidence that the intervening analysis failures may have involved a service-side problem. They are not needed to justify the final result because a subsequent completed StoryCore analysis now passes. + +## Evidence boundary + +The available connector did not expose the detailed text of the original two Sonar annotations, so this report does not claim a proven one-to-one mapping between those annotations and the two path flows. The remediation was the smallest security-oriented change to the executable code introduced in the same change window, and the completed post-remediation Sonar analysis now reports a clean Quality Gate. + +## Current gate + +The Sonar security blocker is closed for the current PR state. It must reopen automatically if a later Harbour change produces a failed or missing required Sonar Quality Gate. + +This does **not** close StoryCore Harbour's separate real-model reliability gate. The immutable acceptance target remains at least 18/20 with median successful completion at or below 180 seconds. diff --git a/apps/storycore-harbour/review/SUBMISSION_BRIEF.md b/apps/storycore-harbour/review/SUBMISSION_BRIEF.md index 5a38193f..e151b526 100644 --- a/apps/storycore-harbour/review/SUBMISSION_BRIEF.md +++ b/apps/storycore-harbour/review/SUBMISSION_BRIEF.md @@ -23,6 +23,17 @@ StoryCore Harbour turns a concept, synopsis, or short script into a coherent vis The deterministic browser demo produces draft versions as `01-concept.png` through `04-continuity.png`. Confirm Anna's exact dimensions and file limits before treating them as final Marketplace assets. +The current revision 11 drafts are 900 x 820 PNG files, each below 106 KB. +Public Anna documentation reviewed on 2026-08-29 did not expose numeric listing +media limits. Use `ANNA_MEDIA_REQUIREMENTS_REQUEST.md` for the prepared support +question; do not upload or submit the drafts until those requirements are +confirmed. + +The authenticated Listing subsequently confirmed PNG/JPG/WebP/GIF logos up to +2MB, cropped to 256 x 256. The validated 5,302-byte candidate is +`marketplace-media/storycore-harbour-logo-256.png`. Screenshot requirements remain the +unconfirmed part of the media gate. + ## Demonstration procedure Follow `../demo/README.md`. The expected reviewer-visible flow takes less than five minutes after dependencies are installed: diff --git a/apps/storycore-harbour/review/marketplace-media/README.md b/apps/storycore-harbour/review/marketplace-media/README.md new file mode 100644 index 00000000..6ef90b85 --- /dev/null +++ b/apps/storycore-harbour/review/marketplace-media/README.md @@ -0,0 +1,26 @@ +# StoryCore Harbour Marketplace media + +## Logo candidate + +- file: `storycore-harbour-logo-256.png`; +- source: `../../bundle/icon.svg`; +- format and dimensions: PNG, 256 x 256; +- size: 5,302 bytes, below Anna's visible 2MB maximum; +- SHA-256: `4a54e1955ac5ebe67eef8c82a260b1b1cdc351a7e87902f2bba2b15532cea7dd`. + +The authenticated Anna Listing inspected on 2026-08-29 accepts PNG, JPG, +WebP, or GIF logos up to 2MB and states that they are cropped to 256 x 256. +This asset is generated from the committed SVG without changing the product +identity. + +Reproduce and validate on Windows with Edge: + +```powershell +$env:BROWSER_EXECUTABLE = 'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe' +npm run marketplace:logo +npm run marketplace:logo:check +``` + +The validator checks PNG signature, exact dimensions, maximum bytes, and +representative gold, white, red, and cyan pixels. Do not upload the file or +save Listing changes without explicit owner approval at action time. diff --git a/apps/storycore-harbour/review/marketplace-media/storycore-harbour-logo-256.png b/apps/storycore-harbour/review/marketplace-media/storycore-harbour-logo-256.png new file mode 100644 index 00000000..3644aa04 Binary files /dev/null and b/apps/storycore-harbour/review/marketplace-media/storycore-harbour-logo-256.png differ diff --git a/apps/storycore-harbour/scripts/browser-smoke.mjs b/apps/storycore-harbour/scripts/browser-smoke.mjs index 8f574340..5e743258 100644 --- a/apps/storycore-harbour/scripts/browser-smoke.mjs +++ b/apps/storycore-harbour/scripts/browser-smoke.mjs @@ -94,6 +94,7 @@ try { await waitForFocus(app.locator("#form-error")); assert.equal(await app.locator("#step-1").isVisible(), true); assert.equal(await app.locator("#step-2").isVisible(), false); + await assertForcedColorsAccessibility(page, app); await fillReferenceProject(app); assert.match((await app.locator("#idea-count").textContent()) || "", /\/ 12,000/); @@ -254,6 +255,7 @@ try { result: "pass", viewport: { width: dimensions.clientWidth, height: minimumFrameSize.height }, textReflow400Percent: "pass", + forcedColors: "pass", formValidationFocus: "pass", keyboardStepNavigation: "pass", panelFocusManagement: "pass", @@ -289,7 +291,10 @@ async function captureMarketplaceScreenshot({ app, frameElement, filename }) { await setFrameSize(frameElement, marketplaceFrameSize); await app.locator("html").evaluate(() => { if (document.activeElement instanceof HTMLElement) document.activeElement.blur(); - window.scrollTo({ top: 0, behavior: "instant" }); + window.scrollTo(0, 0); + if (document.scrollingElement) document.scrollingElement.scrollTop = 0; + document.documentElement.scrollTop = 0; + document.body.scrollTop = 0; }); const path = join(screenshotDirectory, filename); @@ -316,6 +321,43 @@ async function setFrameSize(frameElement, size) { }, size); } +async function assertForcedColorsAccessibility(page, app) { + await page.emulateMedia({ forcedColors: "active" }); + try { + await app.getByRole("button", { name: "Build my visual story" }).focus(); + const state = await app.locator("html").evaluate(() => { + const style = (selector) => getComputedStyle(document.querySelector(selector)); + const activeStep = style(".step.active"); + const disabledStep = style("#step-tab-2"); + const error = style("#form-error"); + const focusedButton = style("#generate-button"); + return { + mediaActive: matchMedia("(forced-colors: active)").matches, + activeOutlineStyle: activeStep.outlineStyle, + activeOutlineWidth: activeStep.outlineWidth, + disabledOpacity: disabledStep.opacity, + disabledBorderStyle: disabledStep.borderTopStyle, + errorBorderStyle: error.borderTopStyle, + errorBorderWidth: error.borderTopWidth, + focusOutlineStyle: focusedButton.outlineStyle, + focusOutlineWidth: focusedButton.outlineWidth, + }; + }); + + assert.equal(state.mediaActive, true, "Edge must activate the forced-colours media query."); + assert.equal(state.activeOutlineStyle, "solid", "The selected step needs a non-colour outline."); + assert.equal(state.activeOutlineWidth, "2px"); + assert.equal(state.disabledOpacity, "1", "Disabled controls must remain legible in forced colours."); + assert.equal(state.disabledBorderStyle, "dashed", "Disabled controls need a non-colour distinction."); + assert.equal(state.errorBorderStyle, "solid", "Errors need a visible forced-colour boundary."); + assert.equal(state.errorBorderWidth, "2px"); + assert.equal(state.focusOutlineStyle, "solid", "Keyboard focus must remain visible in forced colours."); + assert.equal(state.focusOutlineWidth, "3px"); + } finally { + await page.emulateMedia({ forcedColors: "none" }); + } +} + async function installDeterministicTestStorage(app) { return app.locator("html").evaluate(() => { const runtime = window.anna; diff --git a/apps/storycore-harbour/scripts/render-marketplace-logo.mjs b/apps/storycore-harbour/scripts/render-marketplace-logo.mjs new file mode 100644 index 00000000..adb34b74 --- /dev/null +++ b/apps/storycore-harbour/scripts/render-marketplace-logo.mjs @@ -0,0 +1,51 @@ +#!/usr/bin/env node +import { mkdir, readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { resolve } from "node:path"; +import { chromium } from "playwright-core"; + +const APP_ROOT = fileURLToPath(new URL("../", import.meta.url)); +const inputPath = resolve(APP_ROOT, "bundle/icon.svg"); +const outputDirectory = resolve(APP_ROOT, "review/marketplace-media"); +const outputPath = resolve(outputDirectory, "storycore-harbour-logo-256.png"); +const executablePath = process.env.BROWSER_EXECUTABLE; + +if (!executablePath) { + console.error("BROWSER_EXECUTABLE is required to render the Marketplace logo."); + process.exit(2); +} + +await mkdir(outputDirectory, { recursive: true }); +const browser = await chromium.launch({ + executablePath, + headless: true, + args: ["--no-sandbox", "--disable-dev-shm-usage"], +}); + +try { + const page = await browser.newPage({ viewport: { width: 256, height: 256 } }); + const source = await readFile(inputPath); + const sourceUrl = `data:image/svg+xml;base64,${source.toString("base64")}`; + await page.setContent(` + + + `); + const logo = page.locator("#marketplace-logo"); + await logo.evaluate((image, src) => { + image.src = src; + }, sourceUrl); + await logo.waitFor({ state: "visible" }); + await logo.evaluate(async (image) => { + await image.decode(); + if (!image.complete || image.naturalWidth <= 0 || image.naturalHeight <= 0) { + throw new Error("The source logo did not decode before capture."); + } + }); + await logo.screenshot({ path: outputPath, omitBackground: true }); + console.log(JSON.stringify({ result: "pass", width: 256, height: 256 })); +} finally { + await browser.close(); +} diff --git a/apps/storycore-harbour/scripts/validate-marketplace-logo.mjs b/apps/storycore-harbour/scripts/validate-marketplace-logo.mjs new file mode 100644 index 00000000..e2909254 --- /dev/null +++ b/apps/storycore-harbour/scripts/validate-marketplace-logo.mjs @@ -0,0 +1,70 @@ +#!/usr/bin/env node +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { chromium } from "playwright-core"; + +const APP_ROOT = fileURLToPath(new URL("../", import.meta.url)); +const logoPath = resolve(APP_ROOT, "review/marketplace-media/storycore-harbour-logo-256.png"); +const executablePath = process.env.BROWSER_EXECUTABLE; +const bytes = await readFile(logoPath); +const pngSignature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); + +if (!executablePath) { + console.error("BROWSER_EXECUTABLE is required to inspect the rendered Marketplace logo."); + process.exit(2); +} + +assert.ok(bytes.subarray(0, 8).equals(pngSignature), "Marketplace logo must be a PNG file."); +assert.equal(bytes.subarray(12, 16).toString("ascii"), "IHDR", "PNG must start with an IHDR chunk."); +assert.equal(bytes.readUInt32BE(16), 256, "Marketplace logo width must be 256 pixels."); +assert.equal(bytes.readUInt32BE(20), 256, "Marketplace logo height must be 256 pixels."); +assert.ok(bytes.length <= 2 * 1024 * 1024, "Marketplace logo must not exceed Anna's 2MB limit."); + +const browser = await chromium.launch({ + executablePath, + headless: true, + args: ["--no-sandbox", "--disable-dev-shm-usage"], +}); +let samples; +try { + const page = await browser.newPage({ viewport: { width: 256, height: 256 } }); + await page.goto(pathToFileURL(logoPath).href); + samples = await page.locator("img").evaluate((image) => { + const canvas = document.createElement("canvas"); + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + const context = canvas.getContext("2d", { willReadFrequently: true }); + context.drawImage(image, 0, 0); + const pixel = (x, y) => [...context.getImageData(x, y, 1, 1).data]; + return { + naturalWidth: image.naturalWidth, + naturalHeight: image.naturalHeight, + goldBeacon: pixel(128, 56), + whiteMast: pixel(128, 128), + redHull: pixel(128, 152), + cyanWave: pixel(48, 172), + }; + }); +} finally { + await browser.close(); +} + +const near = (actual, expected, tolerance = 12) => + expected.every((channel, index) => Math.abs(actual[index] - channel) <= tolerance); +assert.deepEqual([samples.naturalWidth, samples.naturalHeight], [256, 256]); +assert.ok(near(samples.goldBeacon, [247, 198, 90, 255]), "Logo beacon must render gold."); +assert.ok(near(samples.whiteMast, [244, 247, 251, 255]), "Logo mast must render white."); +assert.ok(near(samples.redHull, [239, 75, 95, 255]), "Logo hull must render red."); +assert.ok(near(samples.cyanWave, [71, 215, 232, 255]), "Logo wave must render cyan."); + +console.log(JSON.stringify({ + result: "pass", + format: "PNG", + width: 256, + height: 256, + bytes: bytes.length, + maximumBytes: 2 * 1024 * 1024, + pixelSamples: "pass", +})); diff --git a/apps/storycore-harbour/tests/acceptance-failure-name.test.mjs b/apps/storycore-harbour/tests/acceptance-failure-name.test.mjs index 92c40de3..a00be603 100644 --- a/apps/storycore-harbour/tests/acceptance-failure-name.test.mjs +++ b/apps/storycore-harbour/tests/acceptance-failure-name.test.mjs @@ -7,7 +7,18 @@ test("contract failure details map to stable privacy-safe names", () => { assert.equal(publicFailureName("JSON parse failed: Unterminated string", "contract"), "json_invalid"); assert.equal(publicFailureName("warnings[0].severity must be info", "contract"), "warning_severity_invalid"); assert.equal(publicFailureName("sceneId references unknown scene secret-scene", "contract"), "reference_invalid"); + assert.equal( + publicFailureName("shot references secret-character, but that character is not listed in the parent scene", "contract"), + "reference_invalid", + ); assert.equal(publicFailureName("Total scene duration is implausibly short", "contract"), "duration_invalid"); + assert.equal(publicFailureName("schemaVersion must be storycore.project.v1", "contract"), "schema_invalid"); + assert.equal(publicFailureName("project.format is unsupported: private-format", "contract"), "schema_invalid"); + assert.equal(publicFailureName("project.createdAt must be an ISO-compatible date-time", "contract"), "timestamp_invalid"); + assert.equal(publicFailureName("Duplicate id at characters[1]: private-id", "contract"), "duplicate_invalid"); + assert.equal(publicFailureName("scenes[0].order must be a positive integer", "contract"), "ordering_invalid"); + assert.equal(publicFailureName("continuityReport.score must be between 0 and 100", "contract"), "continuity_score_invalid"); + assert.equal(publicFailureName("scenes must be an array", "contract"), "structure_invalid"); }); test("unknown contract details never enter the public failure name", () => { diff --git a/apps/storycore-harbour/tests/marketplace-logo.test.mjs b/apps/storycore-harbour/tests/marketplace-logo.test.mjs new file mode 100644 index 00000000..8c6b2232 --- /dev/null +++ b/apps/storycore-harbour/tests/marketplace-logo.test.mjs @@ -0,0 +1,20 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { resolve } from "node:path"; +import test from "node:test"; + +const APP_ROOT = fileURLToPath(new URL("../", import.meta.url)); +const logoPath = resolve(APP_ROOT, "review/marketplace-media/storycore-harbour-logo-256.png"); +const pngSignature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); + +test("committed Marketplace logo is a bounded 256px PNG", async () => { + const bytes = await readFile(logoPath); + + assert.ok(bytes.subarray(0, 8).equals(pngSignature)); + assert.equal(bytes.subarray(12, 16).toString("ascii"), "IHDR"); + assert.equal(bytes.readUInt32BE(16), 256); + assert.equal(bytes.readUInt32BE(20), 256); + assert.ok(bytes.length > 0); + assert.ok(bytes.length <= 2 * 1024 * 1024); +}); diff --git a/apps/storycore-harbour/tests/repair-prompt.test.mjs b/apps/storycore-harbour/tests/repair-prompt.test.mjs index 30413251..cafe33ba 100644 --- a/apps/storycore-harbour/tests/repair-prompt.test.mjs +++ b/apps/storycore-harbour/tests/repair-prompt.test.mjs @@ -21,7 +21,32 @@ test("repair rebuilds from the exact user input without carrying truncated model assert.deepEqual(request.input, input); assert.deepEqual(request.validationErrors, ["JSON parse failed: truncated object"]); - assert.equal(request.task, "Rebuild a complete StoryCore Harbour production package from the source input."); + assert.match(request.task, /Rebuild the complete StoryCore Harbour project/); assert.equal(Object.hasOwn(request, "previousResponse"), false); assert.equal(prompt.includes("PREVIOUS RESPONSE"), false); }); + +test("repair prompt carries a complete structural checklist without weakening the contract", async () => { + const { createRepairPrompt } = await import("../bundle/repair-prompt.js"); + const request = JSON.parse(createRepairPrompt({ + idea: "A sufficiently long fictional source idea for a repair test.", + title: "Repair shape", + format: "short-film", + durationMinutes: 3, + language: "en", + tone: "Clear", + audience: "General audience", + }, ["continuityReport.score must be between 0 and 100."])); + + assert.equal(request.requiredShape.schemaVersion, "storycore-harbour.project.v1"); + assert.ok(request.requiredShape.project.includes("sourceIdea")); + assert.ok(request.requiredShape.productionBible.includes("visualDirection.cameraLanguage")); + assert.ok(request.requiredShape.shot.includes("dialogue")); + assert.ok(request.requiredShape.shot.includes("sound")); + assert.deepEqual(request.requiredShape.continuityReport, ["score", "warnings[]", "checkedAt"]); + assert.ok(request.hardRules.some((rule) => /exactly 3 scenes/i.test(rule))); + assert.ok(request.hardRules.some((rule) => /dialogue and sound are always strings/i.test(rule))); + assert.ok(request.hardRules.some((rule) => /warning severity is only info, warning, or error/i.test(rule))); + assert.equal(request.discardPreviousResponse, true); + assert.equal(request.sizeBudget.maxCharacters, 12_000); +});