diff --git a/.agents/inbox/2026-10-23-browser-startup-evidence.md b/.agents/inbox/2026-10-23-browser-startup-evidence.md new file mode 100644 index 00000000..98027a85 --- /dev/null +++ b/.agents/inbox/2026-10-23-browser-startup-evidence.md @@ -0,0 +1,74 @@ +# Distinguish the static loading shell from a mounted application and retain failure traces + +Date: 2026-10-23. Searched `tests/e2e/quality/button-content.spec.ts`, the unified +manifest/adapter, Playwright configuration, prior responsive acceptance lessons, +`packages/web/index.html`, and `App.tsx`. Public tracking: #156. + +The strict combined acceptance run on frozen +`f8a11e16a593738d485547eb1dfe117438cc3ed6` reported this failure before button +inspection on one route/viewport iteration: + +```text +Locator: locator('main') +Timeout: 5000ms +Error: element(s) not found +Snapshot: GraphDone Logo / GraphDone Loading... +``` + +The screenshot and DOM snapshot exactly match the static HTML shell in +`packages/web/index.html`: Loading text and empty placeholder cards. React's +authenticated initialization screen instead says Initializing and includes +feature text; route Suspense appears inside Layout's main element. Thus this +failure establishes that the mounted application was unavailable at the +deadline, not that a button label was clipped. It does not by itself distinguish +delayed module loading/execution from a startup error. + +The screenshot timestamp was 12:53:58 America/Los_Angeles. Docker build/export +work overlapped the run and the generic image build log completed at 12:55:38. +Resource contention is a hypothesis, not a proven cause. The original artifact +did not identify the current route or exact viewport; its 375-by-1144 full-page +PNG dimensions do not establish the viewport. No trace existed because the +global setting was on-first-retry while the strict sequence used zero retries. +Artifacts remain in the candidate worktree's ignored +`test-artifacts/playwright-output/quality-button-content-*/` directory and must +be preserved before another run. + +Corrective rule: keep zero retries and existing deadlines, but record +retain-on-failure traces for required browser sequences. Label every route and +viewport iteration and attach bounded browser errors when the sweep fails. +Copy trace/diagnostic files into the retained unified report before another +Playwright process can clear its shared output directory. Do not equate the +absence of API errors with successful frontend startup. + +Evidence-only correction on public PR175 base `1a16d4e`: three new controlled +child-process tests first failed because the actual Playwright command lacked +failure-only tracing. A fourth failed because the adapter dropped trace and JSON +attachments. After correction, Node 22.23.4 `npm run test:unified:lib` passed +156/156 with zero skips, including bounded event capture, credential/URL +sanitization, listener cleanup, and file retention after deleting the original +Playwright output. Playwright `--list` still collected the single unchanged +button sweep. Logs: `/tmp/core-browser-diagnostics-{red,retention-red,final}.log`. + +Independent review then reproduced a source-boundary defect inherited from the +image/video materializer: an attachment could name an unrelated local file or +follow a symlink, copying it into a downloadable report. Owned outside-root +sentinels established the failure without reading real secrets. Four new CLI +controls failed before correction (outside-root source, file symlink, parent +directory symlink, and directory instead of a file). New downloadable files now +require regular files under the canonical repository `test-artifacts` root with +no symlink traversal; the adapter rejects unsafe evidence and the materializer +rechecks immediately before copying. The legitimate copy/retention case still +passes. Final stdlib validation: 160/160 passed, zero skips, recorded in +`/tmp/core-browser-diagnostics-reviewed.log`. + +The custom JSON diagnostic capture applies bounded credential/URL redaction; +Playwright trace ZIPs remain raw and can contain network headers and bodies. +Retain those locally with the disposable test artifacts and share measured +summaries, not raw authenticated traces. Standalone and dashboard report views +both render these new files as escaped download links rather than images. + +Limits: these checks validate evidence collection and command forwarding, not +the underlying startup failure or deployed health. No browser was launched and +no application code, deadline, retry policy, or assertion threshold changed in +this correction. A serialized complete acceptance rerun is still required; +retain the earlier failed run and distinguish observations from causal claims. diff --git a/.agents/inbox/2026-10-23-core-startup-and-save-evidence.md b/.agents/inbox/2026-10-23-core-startup-and-save-evidence.md new file mode 100644 index 00000000..8909b857 --- /dev/null +++ b/.agents/inbox/2026-10-23-core-startup-and-save-evidence.md @@ -0,0 +1,17 @@ +# Builds and save acknowledgements need boundary-specific validation + +Date: 2026-10-23. Existing guidance searched: `docs/quality/TEST_STRATEGY.md`, +the dated runtime/durability lessons, and `.agents/issues/INDEX.md`. This pointer +keeps the detailed incident records canonical rather than duplicating them. + +| Symptom / cause | Canonical dated evidence, correction and limits | +| --- | --- | +| A successful build hid API peer-resolution and launcher failures | [Runtime validation](../../docs/quality/RUNTIME_VALIDATION_2026-10-23.md): use a pinned clean install, actual startup, supported runtimes and owned browser fixtures. Container/build success alone is not deployment evidence. | +| Missing/default production secrets were accepted; `.env` loaded after key capture | [Production secrets](../../docs/quality/PRODUCTION_SECRETS_2026-10-23.md): validate configuration before authentication initializes; negative controls must stop before DB/listen, with configured positive controls. The later integrated lint check found an ambient type annotation missed by typecheck; the type-only follow-up preserves runtime behavior. | +| A delayed save/refetch erased a newer date edit or repopulated cleared undo history | [Details-save completion](../../docs/quality/DETAILS_SAVE_COMPLETION_2026-10-23.md): preserve newer fields, scope completion to the current editor, require the expected acknowledgement, and retain the undo clear generation. Real Apollo regressions preserve failing-before evidence; they do not replace the combined browser gate. | + +Commands, observed outputs, source revisions and scope limits remain in those +dated records and the [Core merge-validation ledger](../../docs/quality/CORE_VALIDATION_2026-10-23.md). +Keep failed runs and synthetic execution failures distinguishable from product +cases. Carry these pointers and their regressions through public review; neither +an unpublished local note nor a pending ledger field is a release pass. diff --git a/.agents/inbox/2026-10-23-dependency-audit-scope.md b/.agents/inbox/2026-10-23-dependency-audit-scope.md new file mode 100644 index 00000000..f7e89265 --- /dev/null +++ b/.agents/inbox/2026-10-23-dependency-audit-scope.md @@ -0,0 +1,11 @@ +# Audit counts need source reachability and bounded regression evidence + +On 2026-10-23, the unchanged runtime lock at `6b2a007` produced 23 full-audit package entries and eight production-only entries. An isolated lint-tool correction reduced the full count to 17, while production remained eight. The canonical [security scope](../../docs/quality/SECURITY_SCOPE_2026-10-23.md) records families, priorities, authentication probes, and limits. + +Evidence: `npm audit --package-lock-only --json`, `npm audit --omit=dev --json`, a clean lockfile install, `npm run lint`, `npm run test:unified:lib` (106 passed), and `npm run test` (434 passed, 25 database-gated skips). The minimatch regression fails against 9.0.3 at its bounded two-second child deadline and passes against the scoped 9.0.7 override. A separate real in-memory SQLite probe returned two successful verifications of one magic-link token; a synthetic CAPTCHA code was also accepted. No deployed credentials or shared database were inspected. + +Cause: advisory severity and dependency classification do not describe the actual call site. Conversely, a browser-only lockout and a green production dependency gate do not establish backend abuse controls. Historical documentation had described UI attempt tracking as completed brute-force protection. + +Corrective rule: record the exact source/lock snapshot, distinguish package entries from distinct vulnerabilities, trace runtime inputs and deployment packaging, reproduce suspected application gaps in owned fixtures, and keep version updates narrow enough to validate. Preserve negative controls in the existing harness; do not suppress findings by relabeling dependency categories. + +Limits: reachability conclusions are bounded source observations, not proof of non-exploitability. Authentication follow-ups remain open. The security-scope document is the reviewable record; local log paths alone are not published validation. diff --git a/.agents/inbox/2026-10-23-dev-admin-integration.md b/.agents/inbox/2026-10-23-dev-admin-integration.md new file mode 100644 index 00000000..86b6436f --- /dev/null +++ b/.agents/inbox/2026-10-23-dev-admin-integration.md @@ -0,0 +1,42 @@ +# Owner-authorized admin integration still needs independent evidence and post-merge checks + +Date: 2026-10-23. Searched `CLAUDE.md`, the symptom index and the existing +live-ref/transport lesson. Earlier audit notes recorded a non-author approval +blocker. The owner then explicitly authorized leaving `dev` administrator +enforcement disabled permanently, with a high testing bar for agent integration. +This changed the operating authorization; it did not create a non-author review. + +The first integration used the native admin merge for +[PR #157](https://github.com/GraphDone/GraphDone-Core/pull/157); no SSH fallback +was needed. Read-only +verification on 2026-10-23 returned: + +```text +gh pr view 157 --repo GraphDone/GraphDone-Core --json state,headRefOid,mergeCommit,mergedAt +state: MERGED +head: 385e7679fabc60b80cd65cf0a1aa4cdf3d592514 +merge: 9068cd31824991330c6d92196654e53e04e29063 +mergedAt: 2026-10-23T22:22:22Z + +git ls-remote origin refs/heads/dev +9068cd31824991330c6d92196654e53e04e29063 refs/heads/dev +``` + +The merge parents are `8a7de052eb60fe517f45681dc42b5b169d09c660` and the frozen +`385e767` head. Its complete tree `6bf014bcc30eb6e21c0deeae4db9c7d4effeaac2` +equals that validated source. A read-only protection query confirmed +`enforce_admins: false`, configured approval count 1, and force pushes/deletions +disabled. The owner-directed exemption is not an approval record. Post-merge +CI was still running when this lesson was prepared; issue closure remains gated +on its actual result. + +Corrective rule: use the [canonical dev integration playbook](../playbooks/dev-admin-integration.md), +freeze and independently review the diff, preserve failed/passing evidence, +verify source/base/result provenance and complete post-merge checks. Keep the +standing exception scoped to `dev`; do not toggle protection or change identity. +Historical pre-authorization notes remain historical evidence. + +Limits: this authorization does not cover `main`, release or deployment actions, +force pushes, history rewriting, expanded credentials or any other protection +change. The documented docs-only exception requires exhaustive non-document +byte-equivalence proof and independent review; it does not excuse final CI. diff --git a/.agents/inbox/2026-10-23-folder-delete-legacy-hierarchy.md b/.agents/inbox/2026-10-23-folder-delete-legacy-hierarchy.md new file mode 100644 index 00000000..0848bf98 --- /dev/null +++ b/.agents/inbox/2026-10-23-folder-delete-legacy-hierarchy.md @@ -0,0 +1,21 @@ +# Deletion authority includes pre-existing descendant folders + +On 2026-10-23, review of SEC-FOLDER-1 found that validating a folder's owner was insufficient when older records could place another user's folder below it. The new creation/reparenting checks prevent new cross-domain trees, but do not repair existing records. + +## Evidence + +The real in-memory SQLite fixture initializes with `PRAGMA foreign_keys=0`, matching the store's current connection defaults. Deleting an owned parent succeeds and leaves a foreign descendant with a missing ancestor. With `PRAGMA foreign_keys=1`, the declared `ON DELETE CASCADE` removes the foreign descendant. Both the standalone and Neo4j-generated GraphQL schemas reached this SQLite behavior. No shared or production database was used. + +Command: `npm run test --workspace @graphdone/server -- tests/folder-authorization.test.ts` with the new tests copied onto commit `0c2322cdfb2669c7fb2c057ea62df583736aba62`. + +Observed on 2026-10-23: **10 failed, 32 passed**. The failures cover nested foreign descendants with foreign keys off/on, another writable ownership domain, and destinations inside the deleted subtree. With foreign keys on, the foreign descendant query returns an empty array after deletion. A legitimate same-domain cascading deletion remains a passing control. + +After correction, `npm run test --workspace @graphdone/server` reports **70 passed**, including all 42 folder cases; server typechecking passes. Local evidence: `/tmp/graphdone-folder-subtree-baseline-final.log` and `/tmp/graphdone-folder-subtree-full-green.log`. These local paths are evidence pointers, not published validation artifacts. + +## Cause and corrective rule + +Authorization must cover every record a destructive operation can affect, including records connected through database cascades. Before moving any graph references or deleting a folder, recursively enumerate existing descendants, require write access to each, and require the same ownership domain. Reject a graph destination that is the deleted folder or one of its descendants. Use a cycle-safe recursive query so malformed legacy trees cannot loop indefinitely. + +## Limits + +This change does not migrate malformed hierarchies, enable foreign-key enforcement, or redefine valid same-domain folder deletion. It tests actual SQLite and both GraphQL schemas; graph metadata and the Neo4j version probe are simulated. Live backend and browser validation remain separate release gates. The repository had no `.agents/WRITING-IT-DOWN.md` or existing lesson index when this incident was recorded; the related API guidance and regression suite carry the rule with the fix. diff --git a/.agents/inbox/2026-10-23-magic-link-consumption.md b/.agents/inbox/2026-10-23-magic-link-consumption.md new file mode 100644 index 00000000..2ef8f41b --- /dev/null +++ b/.agents/inbox/2026-10-23-magic-link-consumption.md @@ -0,0 +1,9 @@ +# Consume single-use identity tokens with a guarded database update + +On 2026-10-23, real in-memory SQLite regressions on runtime base `5f0f454` reproduced concurrent magic-link replay and inactive-account acceptance. The selected before-fix run failed two cases and passed one. A further controlled delayed-write regression failed against the first fix `4f4b782` because its expiry timestamp was captured before the statement executed. The final full server run passed 39 cases, including all eleven new magic-link cases. Commands and evidence are recorded in the [review note](../../docs/quality/MAGIC_LINK_REPLAY_2026-10-23.md). + +Cause: selecting an unused token does not reserve it. The previous update neither checked unused/expiry state again nor waited for its outcome before returning identity. + +Corrective rule: atomically transition unused state using the database's execution-time expiry predicate, await completion, and require exactly one affected row before returning or creating identity. Check current account activity. Exercise real concurrent requests, expiry boundaries, and actual database failures; mock success callbacks alone cannot establish single-use behavior. + +Limits: this shared verifier still serves both login and reset flows. Purpose separation, other authentication controls, and deployed HTTP/email behavior remain separate work. A failure after successful consumption deliberately requires a fresh link. No shared database or deployed secrets were used. diff --git a/.agents/inbox/2026-10-23-main-promotion-release-gates.md b/.agents/inbox/2026-10-23-main-promotion-release-gates.md new file mode 100644 index 00000000..a098275e --- /dev/null +++ b/.agents/inbox/2026-10-23-main-promotion-release-gates.md @@ -0,0 +1,80 @@ +# A validated dev tree does not validate main-only publication or persistence + +## Correction and verified runtime evidence — 2026-10-24 UTC + +The original findings below led to the reviewed correction in +[PR #191](https://github.com/GraphDone/GraphDone-Core/pull/191). Publishing is now +read-only build validation; unfinished Release/Deploy actions explicitly fail. +Selected production validation is part of CI Success and runs on main PRs. +SQLite uses the actual server workspace volume path with migration guidance. + +On clean `abc29d9dbe0da966cbc22976feebaa2dfced21c1`, the corrected owned HTTPS +stack passed all six headed smoke cases with zero Playwright retries, all seven +persistence checks, source invariance and owned container/volume/network cleanup. +The initial production browser failure is retained; it exposed a helper that +relied on development quick login instead of selecting the existing Password tab. +[Public result and limits](https://github.com/GraphDone/GraphDone-Core/pull/191#issuecomment-6377298729). + +The actual main merge and its final CI remain tracked in +[#189](https://github.com/GraphDone/GraphDone-Core/issues/189). Exact-SHA publication +and real deployment are intentionally disabled and tracked in +[#190](https://github.com/GraphDone/GraphDone-Core/issues/190). This evidence does +not claim deployment to graphdone.com. The original inspection below is preserved +as historical cause and boundary evidence, not a current list of unfixed defects. + + +Date: 2026-10-23. Searched the symptom index, the dev admin-integration +playbook, and the validation ledger before inspecting main promotion. +The owner requested a clean, quota-conscious finish and promotion of good work +to main. Dev validation is complete; the remaining release work is tracked in +[issue #189](https://github.com/GraphDone/GraphDone-Core/issues/189). + +Dated composition evidence: + +```text +main: 775362eace0723c57b7ba3c7936645c9d0d36cb4 +validated dev: ef495eff15ed7aac51e72dc7a1db1bb96d2b3695 +local normal merge: 88ce10dc8c39b1687c180ff22db20744c4bf2996 +merge parents: main775362ea + dev ef495eff +merge conflicts: none +merge tree: 6bdd9d820f98b9882c3dcf6a110badd88bfe78bc +all 683 tracked entries equal validated dev, including modes/types/blob IDs +``` + +The two main-only commits were merge wrappers whose trees equal their source +parents. Independent review confirmed the candidate's complete composition, +not its release readiness. The two documentation paths added by this handoff +are separate from that original byte-identical candidate; no runtime code was +changed for the checkpoint. + +Read-only source findings at `ef495eff`: + +- `.github/workflows/docker-publish.yml` runs even for main-target PRs and sets + `push: true` before validation; main publication can move `latest`. +- Main-only production smoke has no matching generated admin credential, weak + HTTP readiness and retry-tolerant raw smoke; `CI Success` omits that job. +- Compose mounts SQLite at `/app/data`, while the API workspace resolves its + database under `/app/packages/server/data`; recreation persistence needs an + owned-container negative/positive test. +- Deploy uses a missing root Dockerfile, unpinned CI-completion checkout and + echo/TODO deployment steps. A successful message is not a deployment. + +Cause: development browser/API validation does not exercise the different +production Compose paths, registry event conditions or deployment actions. +A mergeable tree can therefore still have release blockers. + +Corrective rule: inspect event side effects before opening the promotion PR; +make PR builds nonpublishing, pin publication to validated source, repair the +owned production fixture/readiness/result propagation, and verify SQLite +persistence through container recreation. Run one deliberate production-stack +gate after the focused correction. Preserve the existing main protections +unless the owner explicitly authorizes a different admin policy. The standing +dev exemption does not silently extend to main. + +Resume in the order and acceptance criteria in issue #189. Do not open a main +PR while its existing hook still publishes to GHCR. Keep the reviewed source +and this handoff committed; do not force-push or rewrite either history. + +Limits: these are source findings, not newly executed production failures. +No production-stack run, registry publication, deployment or main protection +change was performed for this review. No runtime resources were started. diff --git a/.agents/inbox/2026-10-23-mcp-stdio-diagnostics.md b/.agents/inbox/2026-10-23-mcp-stdio-diagnostics.md new file mode 100644 index 00000000..9ba3af46 --- /dev/null +++ b/.agents/inbox/2026-10-23-mcp-stdio-diagnostics.md @@ -0,0 +1,59 @@ +# Successful MCP calls do not prove a clean stdio protocol + +Date: 2026-10-23. Searched the Core testing guide, MCP contract suite, +`.agents/inbox/`, and symptom index before adding this incident. The writing +procedure was read from a sibling project's `.agents/WRITING-IT-DOWN.md`; Core does not +currently carry that file. + +A compiled-server probe on `b843a1b` (SDK 1.32.2, Zod 4.6.7) initialized, +listed 31 actual tools, read an owned graph, rejected malformed and unknown +calls, and successfully read again. It nevertheless failed the protocol check: +`Memory monitoring started` was written to stdout and produced a client +`SyntaxError`. The SDK reports parse errors through `client.onerror` and keeps +processing later messages, so successful tool results alone hid the defect. +The owned subprocess exited and the probe verified deletion of its one edge, +two work items and graph, in that order. No shared database was used. + +The committed regression extends the existing disposable Neo4j contract. From +`packages/mcp-server`, with explicit owned loopback credentials loaded privately: + +```sh +RUN_DB_CONTRACT=1 GRAPHDONE_DISPOSABLE_NEO4J=1 CI=true \ + npx vitest --run tests/neo4j-contract.test.ts -t 'actual MCP stdio' +# Before the source fix: 1 failed, 8 filtered-out cases skipped (6.67 s). +# Only valid JSON-RPC messages may appear on stdout: +# "Memory", "CPU Monitor", "Recorded write", "Waiting for" are not valid JSON. + +RUN_DB_CONTRACT=1 GRAPHDONE_DISPOSABLE_NEO4J=1 CI=true \ + npx vitest --run tests/neo4j-contract.test.ts +# After the source fix: 9 passed, 0 skipped (8.82 s). +``` + +The test launches the actual entry point with `node --import tsx src/index.ts` +so the existing CI contract job does not require a compiled build. It checks +initialization, real tool definitions, an owned node creation/read, independent +Neo4j readback, invalid-call rejection, continued valid use, zero stdout parse +errors through child shutdown, retained stderr diagnostics and owned-child +termination. It shares the suite's explicit disposable-DB guard. Independent +review caught that inherited cleanup swallowed failures; this fixture now uses +its own verified edge → node → graph cleanup, fails on an unconfirmed stage, +and retains the remaining ownership links. The new case creates no edges. Both +the final negative control and passing run confirmed cleanup before checking +the protocol-error channel. The implementation changes only eight operational `console.log` +calls to `console.error` in the memory, CPU and consistency utilities. + +Keep stdout exclusively for MCP protocol messages. Verify both real results +and the client's protocol-error channel at this boundary. Do not silence the +errors or remove diagnostics to make the check pass. + +Limits: this does not test authentication, authorization, every tool, sustained +load, forced garbage collection, or all CPU modes. The CI fixture uses the +existing CI CPU policy; production thresholds are unchanged. The installed +SDK currently maps a malformed request's schema error to `-32603` before the +more specific tools wrapper runs; this patch does not change that behavior. +Two initial standalone probe mistakes (assuming `-32602`, and comparing an +unordered Neo4j status collection without normalization) were corrected after +source inspection and retained separately from the stdout failure. The package unit run passed 97 cases with 26 explicitly DB-gated skips; +package build and typecheck passed, and lint passed with zero errors and 31 +existing warnings. The full combined dependency/stdio candidate still needs +its own merged-head validation. diff --git a/.agents/inbox/2026-10-23-mobile-audit-fixtures.md b/.agents/inbox/2026-10-23-mobile-audit-fixtures.md new file mode 100644 index 00000000..e92351c5 --- /dev/null +++ b/.agents/inbox/2026-10-23-mobile-audit-fixtures.md @@ -0,0 +1,45 @@ +# Mobile audit controls must target owned content and observable dismissal + +Date: 2026-10-23. Existing guidance searched: the responsive acceptance lesson, +`docs/quality/TEST_STRATEGY.md`, and `tests/lib/mobileAudit.ts`. Tracks #156. + +The existing CI mobile selection ran on combined source `aece679` with one headed +Chromium worker, zero retries, and a diagnostic stop after three failures: + +```text +npx playwright test --grep='@mobile|@audit' --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 --max-failures=3 +15 passed, 3 failed, 9 did not run (3.4m), exit 1 +``` + +The edit-dialog test selected an obsolete grid wrapper in an arbitrary default +graph. The create-dialog selector matched both a hidden desktop button and the +visible mobile button; its expected heading was also obsolete. The navigation +test pressed Escape (which closed More), then blindly clicked the underlying +page at fixed coordinates, opening a work item and blocking the next nav click. + +The two dialog cases now create their own graph and named task, select actual +visible controls/current headings, retain every viewport/paint-order assertion, +and clean up only that graph's fixture, deleting edges before work items and +the graph so failed cleanup preserves ownership links. The navigation case asserts that Escape +hides More, removing the blind follow-up click and swallowed keyboard failure. + +The final functional patch ran against the unchanged application on +2026-10-23, Node22.23.4, bundled Chromium, localhost API/owned Neo4j: + +```text +TEST_URL=http://127.0.0.1:13127 QC_API_URL=http://127.0.0.1:14127/graphql xvfb-run --auto-servernum npx playwright test tests/e2e/mobile/dialog-positioning.spec.ts tests/e2e/mobile/experience.spec.ts --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 +4 passed (35.5s), exit 0 +``` + +Corrective rule: own mutable fixtures, observe the visible control, and assert +dismissal before the next action. Coordinates without a current target can +activate newly exposed content. Keep failed evidence; a focused pass does not +count the nine unexecuted mobile cases as passing. Final combined mobile CI is +still required. This emulated browser evidence does not establish native mobile +keyboard or screen-reader behavior. Logs/screenshots are retained in the owned +worktrees' ignored `test-artifacts/audit-20261023/` directories and summarized in +public review comments. + +Independent review required the repository's edge-first cleanup rule. After +adding graph-scoped edge deletion before tasks/graph, the same four headed +cases passed again in 35.6 seconds on the unchanged application (exit 0). diff --git a/.agents/inbox/2026-10-23-mobile-touch-editor.md b/.agents/inbox/2026-10-23-mobile-touch-editor.md new file mode 100644 index 00000000..a5744f76 --- /dev/null +++ b/.agents/inbox/2026-10-23-mobile-touch-editor.md @@ -0,0 +1,83 @@ +# Locked touch acceptance must verify the current editor and durable owned work + +Date: 2026-10-23. Searched `CLAUDE.md`, the responsive/mobile acceptance lessons, +`docs/quality/NEW_USER_GATE.md`, `touch-graph.spec.ts`, the graph's touch handlers, +`QuickEditSurface`, and `NodeQuickEdit`. Tracks public issue #156. + +The strict mobile run on clean source +`f8a11e16a593738d485547eb1dfe117438cc3ed6` completed 27 actual cases: 26 passed +and one failed, with zero skipped cases. Its unified report also added an +execution-evidence failure for the nonzero Playwright exit; that synthetic row +is not a second failed browser case. Starting and ending source fingerprints +matched (`5846dde3dcce32baf391d875b32c6e437723ade56c50292848ad5b1b84ebcc0e`). + +```text +mobile/touch-graph.spec.ts: +single tap on a node opens the inspector (even while locked) +getByTestId('node-inspector'): element(s) not found after 8000 ms +``` + +The retained screenshot shows the quick editor for a default tutorial item, +including its title, description, type, and status controls. Source confirms +that both a direct `touchend` tap and a touch compatibility click intentionally +call `setQuickEditNode`; `QuickEditSurface` is the accessible `Edit work item` +dialog. Ordinary mouse selection still uses the inspector path. The failed +test asserted a historical component identity and selected arbitrary default +data, rather than verifying the current touch editing outcome. + +The focused correction creates one owned graph/task with known saved +coordinates, selects it explicitly, checks its rendered ID, and asserts Locked +before and after actual touch taps. It requires the visible current editor and +the correct title, edits the title, validates the exact single-item HTTP and +GraphQL acknowledgement, and independently reads the persisted task. Reloading +and tapping again must show the saved title while saved coordinates remain +unchanged. Cleanup confirms edges, task, then graph in order, retaining remaining +ownership links if a preceding stage fails. + +The existing 90-second test limit, 30-second initial node-visibility wait, +four-second settling wait, and eight-second editor deadline are unchanged. +Independent source review found no threshold weakening. Actual Playwright +collection remains one touch case. The failed report and screenshot are retained +in the owned worktree as `test-artifacts/audit-20261023/mobile-before-report.json` +and `mobile-touch-before.png`. + +Two focused test-development iterations reached the successful save and API +readback but failed while checking the reloaded SVG label. Raw group textContent +concatenates separate line elements without spaces. The first attempted fix +incorrectly assumed tspans. Inspection of both title creation and update paths +confirmed separate `text.node-title-text` elements; the final assertion joins +those actual rendered line texts with spaces and requires exact full-title +equality, retaining the same five-second assertion deadline. Both failed logs, +JSON reports, and traces remain as `touch-final*` and `touch-verified*`. + +The final functional patch ran against unchanged combined application +`e120df4b849969854ffe529a64df3d42498ae4cf` on 2026-10-23, Node 22.23.4, +bundled Chromium, and the owned localhost API/Neo4j stack: + +```text +TEST_URL=http://127.0.0.1:13127 QC_API_URL=http://127.0.0.1:14127/graphql \ +xvfb-run --auto-servernum npx playwright test tests/e2e/mobile/touch-graph.spec.ts \ + --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 --trace=on +1 passed (14.7s), 0 skipped, 0 retries +``` + +The saved readback contains exactly the created task, title `Saved by a locked +touch`, status `PLANNED`, and unchanged position `(400, 300, 0)`. Touch capability +was enabled, Locked assertions passed, the reloaded editor displayed the saved +title, and all edge/task/graph cleanup counts passed. The JSON readback is +attached by artifact path for retention in unified reports. Final log, JSON, +readback, and trace are retained as `touch-complete*`. No test assertion changed +after this passing run; only this evidence note was completed. + +Corrective rule: identify the intended input-specific user outcome in source, +then assert owned content and durable effects. Replacing an obsolete selector +alone is insufficient. Use touch input rather than a synthetic mouse click, and +do not unlock the graph merely to make a tap test pass. +Inspect the renderer before choosing SVG text selectors; do not guess its line +element structure from a concatenated string alone. + +Limits: this browser-emulated touch check does not establish native mobile +keyboard, assistive-technology, or physical-device behavior. It does not test +dragging, panning, multi-touch gestures, or access-control boundaries. The focused +pass does not replace the final complete mobile gate. No application +implementation change is part of this test correction. diff --git a/.agents/inbox/2026-10-23-password-login-accounting.md b/.agents/inbox/2026-10-23-password-login-accounting.md new file mode 100644 index 00000000..3bd80fa2 --- /dev/null +++ b/.agents/inbox/2026-10-23-password-login-accounting.md @@ -0,0 +1,23 @@ +# Reserve failed-login capacity before awaiting password verification + +On 2026-10-23, the baseline `5f0f454` password resolver allowed direct GraphQL +guesses despite the browser's attempt tracker. The initial command +`vitest run packages/server/tests/password-login-limits.test.ts --config packages/server/vitest.config.ts` +reported seven failed tests and one passing successful-login test. After the +server-side correction, the expanded resolver and accounting suites passed all +16 tests using real in-memory SQLite and an owned loopback HTTP fixture. + +Cause: browser state and unrelated REST route limits did not cover password +login. Counting failures only after asynchronous verification would also allow +concurrent attempts to exceed the intended budget. + +Corrective rule: reserve capacity before expensive work, settle it exactly +once, key known accounts by canonical identity, and retain an independent IP +budget across successful logins to other accounts. Bound the tracking maps, +keep blocked keys until expiry, and test direct API aliases and concurrent +calls without relying on browser storage. + +Policy, evidence, and limits are in the canonical +[password-login review](../../docs/quality/PASSWORD_LOGIN_LIMITS_2026-10-23.md). +This is per-process accounting, not distributed protection; the CAPTCHA and +other authentication gaps remain separate work. diff --git a/.agents/inbox/2026-10-23-performance-gate-evidence.md b/.agents/inbox/2026-10-23-performance-gate-evidence.md new file mode 100644 index 00000000..ce4825d8 --- /dev/null +++ b/.agents/inbox/2026-10-23-performance-gate-evidence.md @@ -0,0 +1,83 @@ +# Required performance gates must measure owned data and publish short-lived simulations + +Date: 2026-10-23. Searched `docs/testing/README.md`, `tests/README.md`, the +unified manifest, prior responsive acceptance lessons, and performance specs. +Tracking: public issue #156. + +The three-case performance suite selected existing/default graphs, skipped empty +graphs and missing drift metrics, and timed a `workItems` query without checking +HTTP status, GraphQL errors, or returned data. Its name incorrectly claimed to +measure `get_graph_context`. Both performance and mobile were declared optional +in the unified manifest even though CI required them using raw Playwright exits. + +Before correction, `node --test tests/lib/runner/unified-cli.test.mjs` failed the +three new regression cases: both required sequences accepted a successful child +process reporting only skipped cases, and their manifest selection was optional +with retries inherited from CI. After correction on combined base `33b3016`, +`npm run test:unified:lib` passed 151/151 tests, including bad metric/data samples +and staged cleanup failure controls. The exact public head `4a4a11f`, rebased +without functional changes onto `5f0f454`, passed 150/150: that base excludes an +unrelated minimatch harness regression. Actual Playwright `--list` collected +3 performance and 27 mobile cases. +Commands ran on Node 22.23.4; regression logs are retained under +`/tmp/core-perf-gate-{red,lib-final}.log` in the audit environment. + +The corrected three browser cases create four owned placed nodes and three edges, +assert their rendered identities and saved positions, and require real metrics. +On unchanged application `aece6797ef61b21665c924762fb8c9ff68870c3a`, this command +used the owned localhost stack, bundled Chromium, and no test retries: + +```text +TEST_URL=http://127.0.0.1:13127 QC_API_URL=http://127.0.0.1:14127/graphql \ +xvfb-run --auto-servernum npx playwright test tests/perf/graph-perf.spec.ts \ + --project=perf --headed --workers=1 --retries=0 --trace=on +2 failed, 1 passed (49.9s); 0 skipped +Both failures: PerfMeter must publish metrics for the fixture +``` + +The fixture, rendered nodes/edges, and saved-coordinate setup assertions passed. +All 20 exact-data API latency samples passed, and all fixtures were cleaned. +The failed traces and JSON/log are retained in the owned performance worktree's +ignored `test-artifacts/audit-20261023/perf-initial*` outputs. + +Cause: the first publication waited until `performance.now() > 2000`, while a +fully placed simulation can end after its first real tick before that threshold. +Unplaced simulations can also stop between periodic reports, leaving stale +pre-settlement metrics. The correction publishes the first real measured tick +and a terminal snapshot, rejects inactive/wrong-graph simulations and zero +samples, and clears old metrics when stopping/replacing/emptying the simulation. +The browser gate also requires the matching graph ID and terminal snapshot. + +Post-fix validation used the same three cases at exact test revision `4a4a11f` +against clean combined application `b009245035559fccadd350a02c106b6da42347c6`: + +```text +Same headed command, one worker, zero retries: 3 passed (33.9s); 0 skipped +Both metric cases: 4 nodes, 3 edges, 1 actual tick sample, alpha 0, settled true +Average measured tick: 0.2 ms; measured saved-snapshot drift: 0 px +20 successful exact-data PerfWorkItems responses: p95 13.7 ms +``` + +Each snapshot carried its own matching graph ID. The saved-position case also +compared all four live world positions with the known fixture coordinates and +verified unchanged persisted coordinates through the API. All fixture cleanup +stages passed. JSON attachments, traces, and logs are retained alongside the +negative control as `perf-final*`; the public PR records the review and outcome. +Fresh owned `npm ci --legacy-peer-deps`, core build, 275/275 web tests, and web +typecheck passed on the scoped public head. Initial checks using an incomplete +dependency link failed to resolve nested AJV; subsequent checks used this +branch's pinned dependencies and built workspace types. + +Corrective rule: required suites must fail on missing evidence, use owned +deterministic fixtures, verify every timed response, and identify the operation +actually measured. A measured zero differs from no samples. Keep existing +budgets. Preserve parent relationships if child cleanup fails, so the fixture +remains identifiable; independent review caught and corrected best-effort +cleanup that could have detached surviving children. + +Limits: passing the small placed fixture cannot establish large-graph or unplaced +layout performance, MCP latency, distributed service capacity, or deployed-site +health. One measured pinned tick is not a frame-rate benchmark (the meter's FPS +is zero because there is no inter-frame interval). The combined application and +test revisions above identify the browser evidence; final integrated acceptance +and public merge validation remain separate gates. diff --git a/.agents/inbox/2026-10-23-pr-base-ref-is-not-live-tip.md b/.agents/inbox/2026-10-23-pr-base-ref-is-not-live-tip.md new file mode 100644 index 00000000..eb15d70d --- /dev/null +++ b/.agents/inbox/2026-10-23-pr-base-ref-is-not-live-tip.md @@ -0,0 +1,102 @@ +# Verify live refs and transport authority before accepting a merge + +Date: 2026-10-23. Searched `.agents/` and `docs/quality/` for `baseRefOid`, +`ls-remote`, Git ref APIs, branch-tip and merge-inventory guidance. No existing +lesson described this mismatch. + +At 20:40 UTC, the read-only merge inventory observed these responses. A separate +read-only recheck during the same audit returned the same values: + +```text +gh pr view 158 --json number,baseRefName,baseRefOid +number: 158 +baseRefName: audit/core-validation-20261023 +baseRefOid: a6d2d4138daf31fb07d8727fc33779a9bfdb1404 + +gh pr view 160 --json number,baseRefName,baseRefOid +number: 160 +baseRefName: audit/core-validation-20261023 +baseRefOid: a6d2d4138daf31fb07d8727fc33779a9bfdb1404 + +gh pr view 161 --json number,baseRefName,baseRefOid +number: 161 +baseRefName: audit/core-validation-20261023 +baseRefOid: 100625e58a01d282082e9d1fd74d61020f01f842 + +git ls-remote origin refs/heads/audit/core-validation-20261023 +100625e58a01d282082e9d1fd74d61020f01f842 refs/heads/audit/core-validation-20261023 + +gh api repos/GraphDone/GraphDone-Core/git/ref/heads/audit/core-validation-20261023 +ref: refs/heads/audit/core-validation-20261023 +object.type: commit +object.sha: 100625e58a01d282082e9d1fd74d61020f01f842 +``` + +The inventory stopped at the mismatch; no branch mutation occurred during this +check. The workflow assumption was the problem: it used a PR's `baseRefOid` as +proof of the live target-branch tip. The observed PR responses did not agree +with one another, while the Git remote ref and GitHub Git-ref API agreed. + +Corrective rule: resolve the named target branch through its actual Git ref +before composing or merging reviewed heads. Record that tip and the exact PR +heads, stop if independent ref reads disagree, and verify the resulting merge +head and tree against the reviewed composition. Do not substitute a PR's +`baseRefOid` for the live branch ref or assume a successful merge command proves +the expected content. + +Limits: these are the responses observed in this audit. They do not establish +why the API values differed, a GitHub defect, or that every PR response is stale. +A ref can change after a read, so a later merge still needs its own resulting +head/tree verification. No remote-metadata mocking test was added; it would not +prove the state of the actual public branch. + +## A workflow-scoped API refusal needs its own evidence + +Later on 2026-10-23, the merge helper attempted the exact reviewed PR #170 head: + +```text +gh pr merge 170 --repo GraphDone/GraphDone-Core --merge --match-head-commit bc483d21d74e787d7807a909757fb2538e0cea14 + +GraphQL: refusing to allow an OAuth App to create or update workflow `.github/workflows/ci.yml` without `workflow` scope (mergePullRequest) +``` + +The helper `/tmp/graphdone-audit-merge.py` raised `RuntimeError` and stopped. This +response establishes a refusal for that OAuth merge operation, not completion +of the merge. No permission expansion or account switch occurred. + +The existing remote was `git@github.com:GraphDone/GraphDone-Core.git`. A read-only +identity check of the already configured SSH credential returned: + +```text +ssh -o BatchMode=yes -T git@github.com +Hi mvalancy! You've successfully authenticated, but GitHub does not provide shell access. +exit: 1 +``` + +That is GitHub's successful identity-only response; it does not prove branch +write authorization. It identified the same `mvalancy` account already used for +the audit. A normal local merge and SSH push to the audit branch was being +reviewed; no such push or merge completion is claimed by this observation. + +Corrective rule: retain the exact rejected operation and stop that attempt. +Before considering an existing alternate transport within the already +authorized task, verify its account and the live refs; do not infer new +permissions from authentication. Any eventual normal merge/push still needs +the expected resulting head/tree, remote-ref verification, and final CI/review. +The protected `dev` branch and its approval requirements remain unchanged. + +Limits: the observed error names the missing OAuth scope, but this record does +not establish all token or branch permissions. It is not evidence of a pushed +merge or a reason to broaden permissions, change accounts, force-push, or +disable branch protection. + +Confirmed follow-up, 2026-10-23: ordinary same-account SSH merge pushes completed +[#170 at `dcbd5e2`](https://github.com/GraphDone/GraphDone-Core/commit/dcbd5e29ae68e90788855d86b15455b2374fdbd6), +[#174 at `5f834b1`](https://github.com/GraphDone/GraphDone-Core/commit/5f834b1947face3b281093a00a1a29e18ab135c2) +and final audit integration +[#161 at `84f1c0d`](https://github.com/GraphDone/GraphDone-Core/commit/84f1c0d132a6fda6a6bc13270363cf18b69a89f0). +The remote audit ref and GitHub merged state were verified. The entire final +tree `f5a394d442193a502ffa144022fcdcfeb8889f90` equals tested candidate +`bdf82ccdd79bbc4c21ce4833023f78596a1be4a8`. These later records establish the +audit-branch outcome; the earlier SSH identity response alone did not. Final +public integration CI and protected `dev` approval remain separate requirements. diff --git a/.agents/inbox/2026-10-23-release-gates-must-fail-closed.md b/.agents/inbox/2026-10-23-release-gates-must-fail-closed.md new file mode 100644 index 00000000..a4745ffd --- /dev/null +++ b/.agents/inbox/2026-10-23-release-gates-must-fail-closed.md @@ -0,0 +1,37 @@ +# A passing aggregate must include every selected production gate + +Date: 2026-10-23 UTC. Scope: GraphDone-Core main promotion, issue #189. + +## Evidence + +At dev `ef495eff15ed7aac51e72dc7a1db1bb96d2b3695`, the CI Success job +omitted prod-smoke from both dependencies and result checks. Docker Publish ran +on main PRs and pushes with `push: true` before CI. Deploy and Release contained +placeholder deployment steps that could report success without deployment. + +Command: `node --test tests/lib/release/workflows.test.mjs`. +The regression applied to the baseline produced 22 tests: 8 passed, 14 failed. +After correcting the workflows the same cases produced 22 passed, 0 failed. +The test executes the aggregate shell under selected/nonselected production +outcomes and checks the publication/deployment boundary. Original local logs: +`/tmp/graphdone-release-gates-before.log` and +`/tmp/graphdone-release-gates-after.log`; the regression is tracked with the fix. + +## Cause and corrective rule + +A success badge only represents jobs explicitly required by its aggregate. +Require the production job when selected, and permit skipped only when it was +not selected. Main PRs need the gate before merge as well as final main CI. + +Keep build validation read-only until an exact-SHA release process has been +implemented and validated. An unfinished deployment must fail explicitly, not +claim success. Main source promotion is not publication or a live-site rollout. +See [release gates](../../docs/quality/RELEASE_GATES.md). + +## Limits + +Workflow tests establish event/result/write guards, not production runtime +correctness. The real isolated production-stack and persistence checks remain +required. Registry publication and deployment are intentionally disabled; +implementing them is a separately tracked release task. Main protection is +unchanged by these source edits; the dev admin exception does not extend it. diff --git a/.agents/inbox/2026-10-23-responsive-acceptance.md b/.agents/inbox/2026-10-23-responsive-acceptance.md new file mode 100644 index 00000000..03e11128 --- /dev/null +++ b/.agents/inbox/2026-10-23-responsive-acceptance.md @@ -0,0 +1,45 @@ +# Responsive acceptance must observe rendered controls and camera translation + +Date: 2026-10-23. Existing guidance searched: `docs/quality/TEST_STRATEGY.md`, +`NEW_USER_GATE.md`, and prior responsive/browser reviews. Public tracking: #156. + +The strict combined run on `aece6797ef61b21665c924762fb8c9ff68870c3a` failed five +cases because tests selected controls by width alone, dragged a locked compact +canvas, measured animated card corners as motion, or expected the graph to be +centered behind reserved control space. A sixth details-save failure is a +separate product race and is not corrected by this test patch. + +A first focused revision passed 6/8: immediate visibility probes ran before +workspace navigation rendered, choosing the hidden desktop control on phones. +Wait for visible navigation before selecting its branch; bound individual +navigation actions so a failure does not consume the whole fixture timeout. + +On 2026-10-23 the final functional patch ran against the unchanged combined +application, using Node 22.23.4, bundled Chromium, localhost, owned Neo4j fixtures, +one headed worker, and zero retries: + +```text +TEST_URL=http://127.0.0.1:13127 QC_API_URL=http://127.0.0.1:14127/graphql \ +xvfb-run --auto-servernum npx playwright test \ + tests/e2e/quality/button-content.spec.ts \ + tests/e2e/quality/graph-motion.spec.ts \ + tests/e2e/quality/graph-resize.spec.ts \ + tests/e2e/quality/new-user-modes.spec.ts \ + --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 +8 passed (2.5m); 653 buttons inspected, 0 content failures +``` + +Corrective rule: wait for actual rendered navigation, verify Editing before a +real drag, measure SVG screen translation separately from decorative scaling, +and require every resize to move the camera while leaving persisted world +positions unchanged. All two fixture cards must remain inside the graph and +clear of visible controls across 12 growth/shrink/rotation steps. Keep drag +persistence, all-view saved-data assertions, and the 17-second motion limits. + +Limits: the motion sample is shorter than the 60-second poll interval; it does +not establish full-poll stability. Browser emulation does not establish native +mobile-keyboard or assistive-technology behavior. These eight passes do not +substitute for the complete strict new-user gate or establish deployment health. +Failed and passing logs/screenshots are retained under the owned worktrees' +ignored `test-artifacts/audit-20261023/` directories; PR review records carry +public progress. The final wording-only corrections clarify these limits. diff --git a/.agents/inbox/2026-10-23-sqlite-volume-workspace.md b/.agents/inbox/2026-10-23-sqlite-volume-workspace.md new file mode 100644 index 00000000..82ffb6e1 --- /dev/null +++ b/.agents/inbox/2026-10-23-sqlite-volume-workspace.md @@ -0,0 +1,76 @@ +# A named volume protects SQLite only when it covers the process workspace + +Date: 2026-10-23. Searched the deployment/runtime lessons and Compose files +before correcting issue #189. The production API image starts +`npm run start --workspace=@graphdone/server`, whose working directory is +`/app/packages/server`; SQLite and generated bootstrap credentials resolve +`data/` from that directory. Three legacy Compose variants instead mounted +`sqlite_auth_data` at `/app/data`. The self-host variant already used the +correct workspace path. + +## Reproduction and correction + +At source `ef495eff15ed7aac51e72dc7a1db1bb96d2b3695`, run: + +```sh +GRAPHDONE_TEST_API_IMAGE=graphdone-audit-patched-api:20261023 \ + node scripts/test-sqlite-container-persistence.mjs +node --test tests/lib/production/sqlite-recreation.test.mjs +``` + +The standalone fixture uses the already built local API image +`sha256:a1972a2814c9cb94ea3265b94398b2e8a90620bb3ebbc24e1133e11cde52a263`. +No image is pulled or published. It derives the volume destination and admin +environment from the actual Compose configuration, creates a unique labelled +volume, and runs the compiled authentication store through the image's npm +workspace in two separately created containers. The first creates an owned +account; the second must retain its ID and validate its password. Both generated +and configured administrator cases also retain their ID. Generated credentials +must keep identical contents and mode 0600. The fixture never prints passwords +or credential-file contents. + +Before the Compose correction, all seven checks failed: three mount checks, +two admin-forwarding checks, and two real recreation cases that lost the owned +account. After changing the mount to `/app/packages/server/data` without +changing the volume key, and forwarding optional production `ADMIN_EMAIL` / +`ADMIN_PASSWORD`, the same seven checks passed. The development Dockerfile +prepares that directory for UID/GID 1001. Artifact files are +`test-artifacts/release-runtime-20261023/sqlite-before.json` and +`sqlite-after.json` in the owned runtime worktree; both exits are retained +(1 before, 0 after). No test-owned volumes or containers remain afterward. + +Four deterministic tests for the full-stack recreation hook pass: stable login +identity, rejection of a newly bootstrapped replacement identity, GraphQL errors +despite HTTP 200, and container-recreation failure. The hook requires actual +HTTP login before and after recreating only the owned API service. Its execution +against the final HTTPS stack is a separate release gate; these unit cases do +not claim that result. + +The development image was also built locally from `8e42a59` with +`docker build --pull=false -f packages/server/Dockerfile.dev -t graphdone-sqlite-dev-test:8e42a59 .` +(exit 0). Image +`sha256:1c464d180bfe705325d7ad88867d497c0751040df46b6a4e5da0aec5c2d0b93a` +passed an additional temporary probe that ran two recreated containers as +UID/GID 1001 through npm's actual workspace and the TypeScript authentication +module. The mounted directory belonged to UID 1001; the owned account ID, +password validation, administrator ID and mode-0600 bootstrap contents survived. +The private fixture environment, both containers and the named volume were +removed. `dev-image-build.log` and `dev-image-persistence.json` in the same +artifact directory retain that bounded check. This additional probe is local +evidence, not a claimed committed HTTP/browser regression. + +## Corrective rule and limits + +Test removal and recreation with the real workspace and volume, rather than +only restarting a process. Preserve the existing volume name. Before applying +this fix to an existing installation, back up the stopped old container's +runtime data directory, inspect existing volume contents, and restore the +chosen data without overwriting an unreviewed copy. Do not delete the old +container or volume before verifying restoration. The +[deployment guide](../../docs/deployment/README.md#preserve-existing-sqlite-data-before-recreating-legacy-api-containers) +records the migration requirement; migration is not automatic. + +The standalone test validates compiled SQLite account/password persistence and +private bootstrap recovery, not the complete HTTP/GraphQL application, Neo4j, +power-loss behavior, backup recovery, concurrent writes or hosted D1 parity. +No shared database, existing volume, browser, registry or deployment was touched. diff --git a/.agents/inbox/2026-10-23-zod-constrained-collections.md b/.agents/inbox/2026-10-23-zod-constrained-collections.md new file mode 100644 index 00000000..04122e79 --- /dev/null +++ b/.agents/inbox/2026-10-23-zod-constrained-collections.md @@ -0,0 +1,104 @@ +# Validate collection limits before walking elements and retain real SDK compatibility evidence + +Date: 2026-10-23. Searched the dependency/security lessons, MCP production +imports, SDK schemas and existing protocol tests. The final production audit +reported [GHSA-mrhx-c7xc-m3w5](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5) +against the locked Zod 3.25.76 package. The advisory lists versions below 4.6.6 +as affected and 4.6.6 as patched. Its later audit appearance does not establish +when the vulnerability became reachable or why earlier audit responses differed. + +The public advisory was published October 1 and reviewed/updated October 23; +it was not newly published when the final check failed. On unchanged +`78a90cc7899e44c33aa27117bccb19ea7cbe85f6`, the fresh production audit exited 1 +with eight moderate findings and this one high finding, while the earlier CI +response contained eight moderate findings only. Repeating with CI's npm +10.9.9 also reported the high finding. The exact registry/cache/backend cause +of the differing responses is unproven. The CI audit step now uses its own +runner-temporary cache for each run attempt, retaining the existing install +cache and unchanged high/critical threshold. This removes restored audit-cache +reuse as one variable; it does not guarantee registry freshness. + +The direct MCP dependency is pinned to 4.6.7. A manifest-only install initially +left the vulnerable 3.25.76 copy hoisted for the SDK and nested the new version +under MCP. A targeted `npm update zod --package-lock-only --ignore-scripts +--legacy-peer-deps` resolved the single root copy instead. The final lock delta +changes only that root version, its registry integrity, and the MCP constraint; +SDK 1.32.2 stays unchanged. `npm ls zod --all --omit=dev` confirms both the SDK +and zod-to-json-schema deduplicate to 4.6.7. Do not equate the manifest pin alone +with remediation of the consumer's resolved dependency. + +The [upstream fix, PR #6652](https://github.com/colinhacks/zod/pull/6652), rejects +oversized collections before parsing their elements when a leading upper-size +check applies. Later refinements do not run after that rejection. Unbounded +containers are unchanged. The regression exercises that narrow contract with +three-element collections, eight invalid array elements and one 101-element +SDK completion response, not an exhaustion-sized input or timing threshold. + +Baseline evidence on clean dependency base +`78a90cc7899e44c33aa27117bccb19ea7cbe85f6`, Node 22.23.4, Zod 3.25.76 and MCP +SDK 1.32.2: + +```text +npm ci --legacy-peer-deps --no-audit --no-fund +added 1222 packages; exit 0 + +npm run test -w @graphdone/mcp-server -- tests/zod-sdk-compatibility.test.ts +13 cases: 5 failed, 8 passed; exit 1 +array/set/map upper bounds: 3 element/key refinement calls, expected 0 +array length 2 with 8 invalid elements: 9 issues, expected one too_big issue +SDK completion maximum 100 with 101 invalid values: 102 issues, expected one +``` + +The final-fixture failing-before log is retained in the owned regression worktree at +`test-artifacts/audit-20261023/zod-sdk-baseline-final.log`; the initial run remains +in `zod-sdk-baseline.log` in the same directory. Install output is retained +as `/tmp/graphdone-zod-baseline-install.log`. The test is +[`packages/mcp-server/tests/zod-sdk-compatibility.test.ts`](../../packages/mcp-server/tests/zod-sdk-compatibility.test.ts). +After cherry-picking the separate manifest/lock correction +`b843a1baef4b5de1efbca3cd73c1a0346806afff` onto the regression branch, the +combined local head was `8d206e2cd2698082ee71d709cd86a3d3d0d7fc58`. A fresh +`npm ci --legacy-peer-deps --no-audit --no-fund` installed Zod 4.6.7 with unchanged +SDK 1.32.2. The same command and unchanged test file then passed **13/13**, +zero failures/skips, exit 0 in 441ms. All five previously failing bounded cases +passed; the eight SDK compatibility/control cases stayed passing. Logs are +`zod-patched-install.log` and `zod-sdk-patched.log` beside the retained baseline. +No production audit or complete application result is inferred from this +focused run. + +No GraphDone application source imports Zod directly. The MCP SDK already uses +`zod/v4` from the 3.25.76 package for the low-level `Server` and stdio schemas; +it accepts either Zod 3.25 or 4. The regression's in-memory SDK client/server +checks initialization, tool listing, nested arguments and metadata, absent +arguments, default result content, malformed calls, invalid results and later +recovery. A small stdio buffer checks invalid-envelope rejection, next-message +parsing and the configured byte cap. These fixture handlers do not start the +GraphDone entry point or prove GraphService/Neo4j behavior. + +Malformed call parameters reject before fixture dispatch. The SDK's existing +low-level parse path maps these schema failures to an MCP error; the tests do +not require a specific message or claim every request error is `InvalidParams`. +Invalid handler results do use `InvalidParams`, and the next valid result still +succeeds. Schema/default compatibility here does not establish every SDK API. + +Core registers `tools/list` and `tools/call`, not completion. The constrained +completion schema is an SDK regression surface, not proof of a reachable Core +completion endpoint. Tool argument declarations remain JSON schemas; upgrading +Zod does not make the low-level server enforce every declared tool constraint +or apply its JSON-schema defaults. Current tool results use an unbounded +content array, and the SDK's stdio byte cap is not a general allocation bound. + +Corrective rule: pair dependency-audit remediation with a small failing-before +behavior test and real consumer compatibility checks. Preserve accepted input, +default and failure behavior without relying on complete error-message text. +Do not describe this bounded-collection correction as complete request-resource +protection, a deployed fix, or a new authentication/data-isolation guarantee. + +The combined candidate `df1fac165a5f1421602c3706db8b86090b54a4f5` subsequently +passed all 603 unit cases (26 gated skips), 161 harness cases and nine real +Neo4j/entry-point contract cases with the separate stderr-diagnostic correction. +Its compiled production-mode MCP probe recorded zero protocol errors and +verified owned child/data cleanup. The fresh-cache production audit exited 0 +with eight moderate findings and no high/critical finding. The consolidated +[validation ledger](../../docs/quality/CORE_VALIDATION_2026-10-23.md#follow-up-the-fresh-production-audit-held-final-approval) +keeps this combined evidence separate from the focused SDK fixture and from +pending final public CI/review. diff --git a/.agents/inbox/2026-10-24-production-login-exercises-manual-mode.md b/.agents/inbox/2026-10-24-production-login-exercises-manual-mode.md new file mode 100644 index 00000000..c8928a41 --- /dev/null +++ b/.agents/inbox/2026-10-24-production-login-exercises-manual-mode.md @@ -0,0 +1,37 @@ +# Development quick login does not verify the manual sign-in path + +The first isolated production run on 2026-10-24 used clean candidate +`83400ac5522c02a80eb50586b187fb33724635fb`. Build, API/Neo4j readiness, generated +admin HTTP login, owned graph seed/readback/cleanup and API recreation passed. +All six actual browser smoke cases then failed; the strict adapter also recorded +the nonzero process exit. The source fingerprint stayed unchanged and owned +containers, volumes and networks were confirmed removed. + +The retained `test-artifacts/release-production/run-rgaac8/smoke/report.json` +reported `passed: 0`, `failed: 7`, `skipped: 0`; each real case said: + +```text +Login failed after 3 attempts for user: admin. Last error: Password field not found +``` + +The first screenshot (`assets/prod-smoke/0.png`) showed the Passwordless tab +selected. Its trace (`1.zip`) recorded repeated visits to `/login-form`, but +`App.tsx` only defines `/login`; the wildcard sends the obsolete route back to +the default sign-in page. `Signin.tsx` defaults `initialMagicLink=true` and +exposes an accessible `Password` tab. The helper's development quick-login path +bypassed mode selection, hiding the defect until production removed those +development controls. + +The correction is in the test helper: navigate to `/login`, explicitly select +the exact Password tab, assert its selected state and the actual labeled form +fields, then enter the generated credentials through the existing form flow. +It changes no application authentication behavior, verification controls, +test deadlines or retries. The unchanged six-case strict production sequence is +the regression: each fresh browser context must use the actual production form. +Its next run must be retained separately; the initial failure is not overwritten +or reclassified as passing. Production evidence remains pending until that run +and its cleanup complete. + +The rule is to exercise each authentication path in the environment where it +exists. Success through a development shortcut says nothing about the manual +form's selected mode, accessible fields or production credentials. diff --git a/.agents/inbox/2026-10-24-production-smoke-needs-owned-runtime-evidence.md b/.agents/inbox/2026-10-24-production-smoke-needs-owned-runtime-evidence.md new file mode 100644 index 00000000..0544fe41 --- /dev/null +++ b/.agents/inbox/2026-10-24-production-smoke-needs-owned-runtime-evidence.md @@ -0,0 +1,62 @@ +# Production smoke must prove its own stack, identity and data + +On 2026-10-24, issue #189's main-release review found that the old `prod-smoke` +job started Compose in the background, accepted any HTTP response from `/health`, +and used `admin/graphdone` against production's randomly generated admin password. +The raw Playwright command also inherited two CI retries. Passing dev CI had not +executed this main-only production path. + +The new `scripts/test-production-stack.mjs` requires clean committed source and +owns a unique disposable Compose project. It builds synchronously, checks real +API and Neo4j health through nginx/TLS, authenticates its generated admin, and +creates and independently reads back two tasks and their edge. Confirmed cleanup +preserves edge → task → graph ownership order. The SQLite recreation checks and +six existing browser smoke cases must then pass; build, startup, readiness, +fixture, skipped/flaky browser evidence, source changes and cleanup failures are +failures of the gate. + +The bounded regression command is: + +```sh +node --test tests/lib/runner/production-stack.test.mjs +# tests 11 +# pass 11 +# fail 0 +``` + +These include real CLI subprocesses with deliberately failing fake Docker +build/start commands, verified project cleanup, generated-secret redaction and +rejection of dirty source before Docker is invoked. Negative controls reject +HTTP 502, static HTML, missing health services, unhealthy Neo4j, wrong saved IDs, +failed child cleanup, external/shared resources and incomplete credential +overrides. This is harness evidence, not a production-stack pass. The broader +Node 22 harness run in this uninstalled worktree was 171 passed / 1 failed: the +dependency regression could not resolve `@typescript-eslint/typescript-estree`. +The installed combined candidate must rerun that full check. + +The corrective rule is to bind acceptance to the exact source and a runtime it +owns, then require functional evidence beyond a responding web server. The +runner changes test isolation inputs only: generated names, local image tags, +private certificates, removed developer `.env` mounts, and a random loopback web +port. Empty frontend URL build arguments select the existing same-origin proxy +fallback so that random port is usable. The actual SQLite target and application +code remain under test. No broad demo seeding or existing database is used. + +Limits: generated secrets and Compose configuration stay outside artifacts and +are removed; console logs redact known generated secrets and JWTs. Raw retained +Playwright traces can contain the disposable account's credentials and network +bodies and must not be published unreviewed. The runner removes its containers, +volumes and networks, retaining local image tags for cache/evidence. It neither +publishes images nor deploys anything. Full-stack evidence must come from a +separate completed run and must name its exact candidate and report. + +## Public evidence excludes raw network traces + +On 2026-10-24 UTC, independent review found that the initial CI directory upload +also included raw failure traces. Those traces can contain the disposable admin +password or token even though stage text logs redact them. The upload now allows +only `run-*/report.json` and `run-*/*.log`; nested smoke reports, ZIP traces and +screenshots stay local. The new workflow regression failed once against the +broad upload (28 other cases passed), then all 29 cases passed with the allowlist. +This preserves public stage outcomes/counts and source/cleanup evidence, not +full per-case raw diagnostics. Runtime, retries and smoke assertions are unchanged. diff --git a/.agents/inbox/2026-10-24-undici-native-build-dependency.md b/.agents/inbox/2026-10-24-undici-native-build-dependency.md new file mode 100644 index 00000000..89440060 --- /dev/null +++ b/.agents/inbox/2026-10-24-undici-native-build-dependency.md @@ -0,0 +1,70 @@ +# A native-build dependency can block promotion even without an application import + +Date: 2026-10-24. Searched the existing dependency-audit and Zod lessons before +changing the main-promotion candidate `d9278cbd7057b9d0d33f078c860596b4176b9b13`. +A fresh production audit reported Undici 6.28.1 through the optional installed +chain `sqlite3@6.0.1 → node-gyp@12.4.0 → undici@6.28.1`. Source inspection found +no direct application import of the npm Undici package. Optional installation +and build-time reachability do not justify removing it from the audit. + +The official advisories identify the affected content-encoding error path in +[GHSA-xjj6-f33q-929c](https://github.com/advisories/GHSA-xjj6-f33q-929c) +and retained EventSource parser state in +[GHSA-4qv8-2p9j-2685](https://github.com/advisories/GHSA-4qv8-2p9j-2685). +Both are fixed on the 6.x line in 6.29.1. The existing node-gyp range +`^6.25.0` accepts that patch. Earlier audit responses differed, but the advisory +publication evidence predates both observed responses; the metadata/cache cause +of that difference is unproven. + +## Dated reproduction and correction + +Node 22, in the owned `core-undici-fix-20261024` worktree: + +```sh +npm ci --legacy-peer-deps --no-audit --no-fund +node --test tests/lib/dependencies/undici.test.mjs +npm audit --omit=dev --audit-level=high --json --cache +npm update undici --package-lock-only --legacy-peer-deps +npm ci --legacy-peer-deps --no-audit --no-fund +npm run test:unified:lib +npm audit --omit=dev --audit-level=high --json --cache +npm explain undici --json +node_modules/.bin/tsx scripts/test-admin-bootstrap.mts +``` + +Before the update, the clean install passed, both new regression cases failed, +and the fresh-cache production audit exited 1 with eight moderate and one high +package findings. Afterward, clean installation and all **207 harness cases** +passed with no skips. The fresh-cache production audit exited 0 with **zero +high/critical and eight moderate findings**; Undici was absent from that report. +Both temporary audit caches were removed. + +The lockfile changes only the version, registry URL and integrity of its single +`node_modules/undici` entry, from 6.28.1 to 6.29.1. No manifest, override, +dependency classification or other package record changes. The regression +rejects the advisory ranges in every locked copy and checks SQLite's actual +node-gyp resolution against the lockfile and its existing dependency range. +It prevents a nested fixed copy from disguising an affected consumer resolution. + +The existing real native-SQLite bootstrap fixture also passed: generated private +credentials, password validation, no development viewer, and stable account +identity across process restarts in its owned temporary directory. Evidence is +retained under `test-artifacts/undici-20261024/`: `regression-before.log`, +`audit-before.json`, `install-before.log`, `install-after.log`, +`harness-after.log`, `audit-after.json`, `resolution-after.json`, +`lock-delta.json` and `sqlite-bootstrap-after.log`. + +## Corrective rule and limits + +Trace the actual installed consumer before making a narrow compatible lockfile +update. Keep the previous failure, use a fresh audit cache, check every resolved +copy, and validate native consumer compatibility. Do not force an audit fix or +remove optional dependencies merely to make the count green. + +These are dependency-version and native-auth-store checks, not an exploit +reproduction, HTTP login, rebuilt-container persistence, hosted deployment or +proof of application non-exploitability. Updating the npm package does not +update Node's separately bundled `fetch` implementation. Full composed CI and +the rebuilt production stack remain separate integration evidence; eight +moderate production findings remain open. No shared database, existing volume, +registry or deployment was touched. diff --git a/.agents/inbox/2026-10-27-captcha-proof-must-be-server-issued.md b/.agents/inbox/2026-10-27-captcha-proof-must-be-server-issued.md new file mode 100644 index 00000000..949c3334 --- /dev/null +++ b/.agents/inbox/2026-10-27-captcha-proof-must-be-server-issued.md @@ -0,0 +1,108 @@ +# Client-side puzzle completion is not a server-verifiable authentication proof + +Date: 2026-10-27. Searched the symptom/security indexes and the October 23 +security inventory before addressing #183. The existing helper accepted any +six uppercase alphanumeric characters; login/signup also skipped the helper +when a payload was omitted. Correct credentials remained necessary for password +login: this was an abuse-control bypass, not proof of account takeover. + +## Reproduction and correction + +With Node 22.18.0 / npm 10.8.2 in an owned worktree based on +`ef495eff15ed7aac51e72dc7a1db1bb96d2b3695`: + +```sh +npm run test -w @graphdone/server -- tests/captcha.test.ts --maxWorkers=1 --minWorkers=1 +# Before: arbitrary code ABC123 was accepted; the new rejection assertion failed. +``` + +The corrected policy requires proof for production login/signup and for Core +magic-link, forgot-password and reset-password requests in every mode. Missing +proof is allowed only for login/signup in explicit development/test mode to +retain development quick login. Supplied proofs are always verified. Guest +access retains its existing separate policy; OAuth callbacks and email-code +verification are not reclassified as these challenge actions. + +The existing installed `altcha-lib@1.4.1` / `altcha@2.3.0` protocol is retained; +no dependency upgrade or provider account is added. The server issues signed +SHA-256 challenges with a five-minute expiry and action. A bounded ledger +authenticates the exact issued salt/signature/action, validates the solution, +rechecks expiry after asynchronous verification and atomically consumes it. +At most 5,000 outstanding/pending challenges are admitted; concurrent issuance +reserves capacity before awaiting cryptography. Live proofs are not evicted to +admit more work. Challenge issuance is limited to 30/minute per observed socket +IP (IPv6 normalized); forwarded headers do not redefine Core's trust policy. + +An unset key generates a random process-local signing key. Explicit keys must +be at least 32 characters; the old public fallback is rejected, including at +production startup. Outstanding challenges intentionally fail after restart. + +## Evidence + +- Original failure retained in `captcha-before.log` under the owned temporary + resolution evidence directory. +- All 139 server tests passed, including nine helper/policy negatives, three + real HTTP GraphQL/SQLite tests, existing password/IP/alias-concurrency tests, + and production startup's fallback-key rejection. +- Three UI component tests cover proof invalidation, action/reset, failed policy + loading and accessible retry. Policy fetch failure never becomes success. +- Four headed browser cases passed through the built frontend served by nginx + and a production-mode API: direct REST/GraphQL omission/forgery rejection; + 390px/1440px real worker-solved proof, failed password then new-proof success; + and policy-load failure with keyboard retry. No email was delivered. +- Clean install, types and build passed; lint passed before the final explicit + socket-IP key generator, whose final composed checks remain to be recorded. +- Earlier browser failures are retained: the test initially forgot to choose + the Password tab; `.check()` incorrectly expected a PoW checkbox to become + checked before asynchronous solving; reusing the same API process exhausted + its existing strict rate limit. The corrected test clicks, waits for verified + status and asserts checked state, against a fresh owned API process. Limits + were not weakened. Initial Vite invocation from the wrong cwd omitted styles; + final evidence uses the built artifact through nginx. + +The browser uses the self-host nginx configuration with only its listener and +API upstream replaced for isolated loopback ports (28548 / 28537). Neo4j/APOC +and SQLite fixtures are owned; the user's localhost application is untouched. +Local traces can include synthetic fixture credentials and remain private. + +## Corrective rule and limits + +Enforce challenge policy at each server action, including direct GraphQL aliases +and the auth-only schema. Client disabling/hiding controls is not enforcement. +Clear consumed/expired proofs and provide a keyboard-accessible retry. Preserve +backend contracts explicitly: Core advertises ALTCHA through `/config`; Cloud's +existing contract remains separate and is not proven by these tests. + +This is a bounded proof-of-work abuse control, not proof that a visitor is human. +The ledger/rate limits are process-local; multiple API instances require a +shared or sticky challenge contract before availability parity can be claimed. +Unresponsive, restarted or over-capacity verification fails closed. Email token +purpose, OAuth linking and reset-session revocation remain #184. Hosted parity, +distributed limits, release/deployment and actual email delivery remain separate +work. Independent exact-head review and final CI are still required. + +## Review corrections + +Independent review of `b12ef2c1f0064ac7cc838ac1e806c52134a6802f` found that +`req.ip` can incorporate caller-supplied forwarding headers with the shipped +`TRUST_PROXY=2` configuration. The challenge limiter now reads only +`req.socket.remoteAddress`; an actual HTTP negative rotates 31 forwarded IPs +under Express trust proxy 2 and confirms that the 31st request is denied. + +Review also found that an enabled password button could spend login attempts +while proof was missing/expired or policy loading failed. The UI now honors +advertised `requiredActions`, disables ordinary submission until ready, and +guards the submit handler too. Tests assert zero Login mutations in those +states, preserving explicitly optional development/legacy actions. A +non-navigating unverified-email result also clears consumed proof. Reset/recovery +forms clear their payload before releasing their loading state. + +The focus regression exposed that ALTCHA's autofocus concerns completion, +not initial mode entry. Initial checkbox focus is now explicit on desktop mode +entry and user-requested retry; typing an email does not transfer focus. The +existing empty-email test now actually leaves and re-enters passwordless mode +instead of clicking the already-selected tab. Both focus tests passed after +that correction. Earlier test implementation errors (Fetch `status` property +versus Playwright's `status()` method and an unsupported Testing Library +`exact` option) are retained in local logs and were corrected before accepting +the results. Final composed evidence belongs in the PR discussion. diff --git a/.agents/inbox/2026-10-27-cold-lod-fixtures-need-separate-budgets.md b/.agents/inbox/2026-10-27-cold-lod-fixtures-need-separate-budgets.md new file mode 100644 index 00000000..7e314b35 --- /dev/null +++ b/.agents/inbox/2026-10-27-cold-lod-fixtures-need-separate-budgets.md @@ -0,0 +1,62 @@ +# Cold database fixture setup must not consume the browser interaction budget + +Date: 2026-10-27. Searched the symptom index and the existing LOD ownership and +performance-evidence lessons. Final PR #192 CI at +`4abce30030e45e1e9d468c67e414302f270d45e9` failed the strict headed new-user +sequence even though the earlier focused local browser run passed. + +## Evidence and cause + +```sh +gh run view 40541503856 --repo GraphDone/GraphDone-Core --job 122322370157 --log-failed +gh run download 40541503856 --repo GraphDone/GraphDone-Core \ + --name smoke-results-e2599a1245884c450fd1635d89b92cc60871a033 --dir +``` + +The basic smoke sequence passed 6/6. New-user reported 43 pass / 2 fail: one +desktop LOD case hit its 120-second whole-test timeout, and the second entry +was the harness's process-exit failure. The phone LOD case passed in 55.3s. +The trace records eight fixture mutation calls taking approximately 5.7–11.6s +each. Those calls consumed most of the shared setup/interaction budget on this +cold database. Navigation, graph switching and camera-restoration assertions +completed; the timeout interrupted the final source-position readback. That is +an incomplete persistence check, not a passing journey or proof of a renderer +hang. Cold query planning is consistent with the earlier local batch-size +incident; this trace does not separately profile the database planner. + +## Corrective rule + +Use a bounded Playwright fixture for expensive real API setup and teardown. +The LOD setup fixture gets 180 seconds; the interaction test keeps +120 seconds, 10-second actions and its existing assertion timeouts. An ownership +dependency fixture has an independent 60-second cleanup budget. Preserve +the 301-item / 370-edge graph, all assertions, and zero retries. Register owned +graph IDs immediately after creation so partial setup can be cleaned. Teardown +runs before the page closes, removes the write guard, deletes edges before +items/graph, verifies graph removal, and surfaces cleanup failures. A teardown +failure must fail the test, not silently leave fixtures behind. + +Independent review caught a flaw in the first correction: Playwright 1.63 shares +a fixture's timeout slot between setup and teardown and can skip teardown after +setup exhausts it. Moving ownership/cleanup into a dependency fixture keeps its +budget independent. Do not describe one fixture timeout as a separate allowance +for each phase. The harness negative control must intentionally time out its +dependent setup and still observe strict edge/item/graph cleanup and readback. + +The next review identified a late-response race: Playwright timeout does not +cancel an in-flight create. Cleanup now first aborts future seed batches and +drains tracked seed promises, retaining the response's graph ID before deletion. +The ownership fixture rejects new seed work once cleanup starts. A deferred-create +negative control releases the response after setup timeout and checks that no +item creation follows and the late graph is still deleted. A request that never +settles can exhaust cleanup's independent budget; that is a reported failed +cleanup, not a claim that remote cancellation or lost-ack recovery was proved. + +## Limits + +This isolates fixture provisioning from interaction correctness; it neither +improves measured application speed nor proves the large-graph performance +target. Rerun the complete strict CI sequence and record its actual result. +Raw CI artifacts are retained under the owned resolution evidence directory; +they are not added to the public repository because traces can contain fixture +sessions. The run link remains the canonical CI failure evidence. diff --git a/.agents/inbox/2026-10-27-delayed-autofocus-must-preserve-chosen-focus.md b/.agents/inbox/2026-10-27-delayed-autofocus-must-preserve-chosen-focus.md new file mode 100644 index 00000000..1545c75e --- /dev/null +++ b/.agents/inbox/2026-10-27-delayed-autofocus-must-preserve-chosen-focus.md @@ -0,0 +1,45 @@ +# Delayed autofocus must preserve focus already chosen by the user + +On 2026-10-27, CAPTCHA PR #195 [CI 40579557476](https://github.com/GraphDone/GraphDone-Core/actions/runs/40579557476) +failed `LARGE-01` because the status-note Add button remained disabled. Its +retained trace shows the note input focused at 746363ms, then the note text +appended to the title at 746402ms. The note input stayed empty. Token PR #197 +[CI 40580018905](https://github.com/GraphDone/GraphDone-Core/actions/runs/40580018905) +also retained a `TEAM-01` disabled-Add failure before the overall new-user +process reached its 900-second limit. Its incomplete JSON report establishes +no passing cases. Related incident: [#200](https://github.com/GraphDone/GraphDone-Core/issues/200). + +## Cause and corrective rule + +`useModalA11y` defers initial focus until an animation frame so a portaled +dialog can mount. That callback unconditionally selected the first form field, +even when someone had already selected the status-note input. Delayed initial +focus must yield to a field already focused in the dialog and to deliberate +focus changes outside it. It may move focus when the opening trigger is still +focused or focus has fallen back to the body. A detached dialog must not take +focus. Existing keyboard trapping, cancellation and close behavior remain. + +## Regression and evidence + +Using Node 22.18.0/npm 10.8.2 in the owned `core-editor-focus-20261027` +worktree, `npm test --workspace @graphdone/web -- +src/components/QuickEditSurface.test.tsx` ran the actual portaled surface and +node editor with a controlled animation-frame queue. Against dev +`97dc8849db8f257ffa88ab790c4452a8022eeaa6`, both chosen-focus cases failed +and four initial-focus/keyboard/cleanup controls passed. With the hook guard, +all six passed. The note case asserts the title stays intact and the only +commit is note metadata. No sleeps, retries, longer timeouts or focus-wait +workarounds were added to the application flow. + +Initial lint rejected the DOM `FrameRequestCallback` type under the existing +no-undef configuration; the fixture uses an explicit local callback type. +Final browser, build, types, lint, full web-unit and harness evidence belongs +in the PR discussion. Retain the failed CI and negative-control results. + +## Limits + +The deterministic test controls frame order in jsdom; actual persona browser +workflows separately cover persistence and desktop/phone interaction. This +does not add background inertness, change the existing Tab trap contract, +prove hosted parity, or turn interrupted CI evidence into a pass. Private +traces can contain authentication state and must not be copied into the repo. diff --git a/.agents/inbox/2026-10-27-dev-launch-must-declare-mode.md b/.agents/inbox/2026-10-27-dev-launch-must-declare-mode.md new file mode 100644 index 00000000..9b06a071 --- /dev/null +++ b/.agents/inbox/2026-10-27-dev-launch-must-declare-mode.md @@ -0,0 +1,46 @@ +# Development launch must declare its mode before authentication policy is read + +On 2026-10-27, [PR #195 CI run40573347298](https://github.com/GraphDone/GraphDone-Core/actions/runs/40573347298) +failed every basic smoke case at authentication on source +`8c9712b7bda0c7515568c08bae710be4bea675a1` (merge composition +`2b16ba2e2318e765883c1fd5ef312a0445addec2`). The sequence reported0 passes, +6 failed cases plus its failed-process evidence row. Trace inspection, with +credentials/tokens omitted, showed `/config` requiring all five CAPTCHA actions, +GraphQL `Please complete verification and try again`, then the existing +password-attempt limiter returning429 after repeated quick-login attempts. +The dedicated production proof/browser tests had passed and did not cover this +development launch contract. + +Cause: CI invokes `npm run dev` without a `.env` or explicit `NODE_ENV`. +The API's old dev command launched `src/index.ts` directly, leaving mode unset. +CAPTCHA deliberately requires proof in unknown modes, while developmentInfo +advertised default-account shortcuts in every non-production mode. Changing +that policy to treat unknown mode as optional would mask the mismatch. + +Correction: the explicit dev command launches `src/dev.ts`. It first loads and +validates environment configuration, defaults only an absent mode to +`development`, then dynamically imports the API. Explicit production, unknown +and blank modes are preserved. Development shortcuts are advertised only for +explicit development/test modes. Production `start` still imports the normal +API directly, and production/unknown CAPTCHA remains required. + +Validation on Node22.18.0: six entry-point controls plus nine production startup +and nine CAPTCHA controls passed (24/24); server typecheck/build passed. An +owned invocation of the actual npm dev command with `NODE_ENV` unset, an empty +owned server data directory, disposable Neo4j and separate loopback ports +reported only magic-link/forgot-password/reset-password as required actions. +The existing browser smoke suite passed6/6 headed, one worker, zero retries, +2.3 minutes. This exercised actual quick login rather than a stubbed frontend. + +The first entry test incorrectly used Vitest1's `stubEnv(undefined)`, which +created the literal string `undefined`; its cached mocked API factory also did +not rerun after `resetModules`. Correct fixtures delete the variable explicitly +and reset the mock factory. Those six fixture failures are retained separately +from the real CI failure. Assertions, challenge difficulty, login budgets and +CI thresholds were not relaxed. Final full server/harness and exact-head CI +results remain in the PR discussion; no deployment or hosted result is claimed. + +Corrective rule: make the launch command's mode explicit without overwriting +operator configuration, then test that command as well as isolated policy. +Keep unknown-mode policy fail closed and record failed mode/shortcut alignment +as an application regression, not merely a flaky test retry. diff --git a/.agents/inbox/2026-10-27-dev-undici-backport.md b/.agents/inbox/2026-10-27-dev-undici-backport.md new file mode 100644 index 00000000..f7b09277 --- /dev/null +++ b/.agents/inbox/2026-10-27-dev-undici-backport.md @@ -0,0 +1,52 @@ +# A reviewed main-promotion patch does not repair an older dev dependency tree + +Date: 2026-10-27. Searched the symptom and security-audit indexes. PR #192's +production audit failed on Undici 6.28.1 although pending main PR #191 already +contained the reviewed 6.29.1 patch. Live dev was +`ef495eff15ed7aac51e72dc7a1db1bb96d2b3695`; it did not contain that patch. + +## Evidence and correction + +```sh +gh run view 40541503856 --repo GraphDone/GraphDone-Core --job 122321255119 --log-failed +git show --stat 8a9c7512b5dedf4020d117887674d378de85667b +git cherry-pick 8a9c7512b5dedf4020d117887674d378de85667b +``` + +Backport head `765cf1985c389f8850a13df338d56c8113350ce7` changes only the single +Undici lock record, its two consumer-resolution regressions and indexed guidance. +The index conflict was resolved by adding the Undici row while preserving dev's +entries; unrelated main-only lesson links were not imported. Historical October +24 evidence remains dated and is not presented as a new run. + +Fresh Node 22.18.0 / npm 10.8.2 validation on the backport: + +- `npm ci --legacy-peer-deps --no-audit --no-fund`: passed, 1,221 packages. +- `npm run test:unified:lib`: 163/163 passed, zero skips. +- `node_modules/.bin/tsx scripts/test-admin-bootstrap.mts`: real native SQLite + password/recovery-file/no-demo-user/stable-restart fixture passed and cleaned. +- `npm run typecheck`, `npm run lint`, `npm run build`: respectively five, four + and four tasks passed. Existing lint warnings remain. +- Fresh-cache `npm audit --omit=dev --audit-level=high --json`: exited zero, + zero high/critical and five moderate findings. Undici was absent; remaining + families were router and UUID/Apollo federation. Counts are dated package + findings, not exploit counts. +- `npm explain undici --json`: the SQLite/node-gyp consumer resolves 6.29.1. +- Independent exact-head agent review: no findings; not a GitHub non-author + approval. PR #194 carries remote checks; pending checks are not passes. + +Sanitized local evidence is in `/tmp/graphdone-resolution-20261027/undici-*`. +Remote CI: [40565630797](https://github.com/GraphDone/GraphDone-Core/actions/runs/40565630797); +Node 22/24 and installer suite: +[40565630734](https://github.com/GraphDone/GraphDone-Core/actions/runs/40565630734). + +## Corrective rule and limits + +Check live branch ancestry before duplicating an existing fix. Backport the +smallest reviewed compatible change, resolve only the actual conflict, and +refresh consumer/runtime/audit evidence on the target composition. Do not merge +unrelated main-promotion work just to obtain a dependency patch. + +This does not close #185/#186, prove application exploitability, update Node's +bundled fetch, publish a release or establish hosted parity. Final branch CI and +integration verification remain required by the dev admin playbook. diff --git a/.agents/inbox/2026-10-27-disposable-git-fixtures-must-own-maintenance.md b/.agents/inbox/2026-10-27-disposable-git-fixtures-must-own-maintenance.md new file mode 100644 index 00000000..53a7a51d --- /dev/null +++ b/.agents/inbox/2026-10-27-disposable-git-fixtures-must-own-maintenance.md @@ -0,0 +1,55 @@ +# Disposable Git fixtures must prevent detached maintenance before cleanup + +Date: 2026-10-27. + +## Evidence + +Comprehensive run [40589229879](https://github.com/GraphDone/GraphDone-Core/actions/runs/40589229879) +on OAuth head `809f17f1301a1354148d2b25114437301dc4f06f` failed its +Node 22.18 harness. Two invalid CLI selection cases completed their assertions, +then `t.after` reported `ENOTEMPTY` removing `.git` and `.git/objects`. +The runner used Git 2.55.0. These invalid options exit before source provenance +collection, whose Git calls are synchronous anyway. + +An independent owned Git 2.55 probe recorded 12/12 fixture commits starting +`git maintenance run --auto --quiet --detach`. Ten traces recorded descendant +activity after the commit parent exit. Repository-local `maintenance.auto=false` +prevented all 12 launches. Strict local removal passed both variants; the exact +CI cleanup error was not reproduced. Temporary probe evidence is at +`/tmp/graphdone-review-git255-mofpt0da/trace-summary.json` on the investigating +host, not a portable or published artifact. + +The committed regression captures `GIT_TRACE2_EVENT` from the real fixture +commit into its ignored `bin/` directory and rejects maintenance child launches. +On local Git 2.43 / Node 22.18, running +`node --test --test-name-pattern='disposable Git fixture' tests/lib/runner/unified-cli.test.mjs` +failed against the old setup because a maintenance child was recorded. + +## Cause and corrective rule + +A synchronous Git parent does not guarantee all its descendants have exited. +Newer Git versions can detach automatic maintenance before the fixture owner +removes its repository. This is a confirmed lifetime hazard and the strongest +explanation of the CI error, not proof of which exact file the CI writer made. +See the [Git maintenance contract](https://git-scm.com/docs/git-maintenance). + +Immediately after initializing a disposable fixture, configure repository-local +`maintenance.auto=false` and `gc.auto=0` before adding or committing files. +Keep strict cleanup, failure propagation, and provenance checks. Do not add +cleanup retries to conceal a live writer or alter global/developer repository +configuration. Observe the actual child process behavior in the regression. + +## Limits + +This applies only to repositories owned by the test fixture. It neither +suppresses external processes nor certifies arbitrary inherited Git hooks. +No application runtime, OAuth behavior, or production Git configuration changes. +Final supported-Node CI remains required before integration. + +## Corrected validation + +The complete CLI suite passed 28/28 on local Git 2.43 and on the owned Git +2.55 build. A pinned clean install followed by `npm run test:unified:lib` +passed 175/175, with no skips, cancellations or retries. Both invalid-option +assertions and strict cleanup remain intact. The correction is isolated to +fixture initialization plus its real-process regression and this guidance. diff --git a/.agents/inbox/2026-10-27-email-purpose-and-revision-are-auth-boundaries.md b/.agents/inbox/2026-10-27-email-purpose-and-revision-are-auth-boundaries.md new file mode 100644 index 00000000..80f3344b --- /dev/null +++ b/.agents/inbox/2026-10-27-email-purpose-and-revision-are-auth-boundaries.md @@ -0,0 +1,65 @@ +# Bind email link purpose and session revision at the database write + +On 2026-10-27, #184 token work started from the reviewed CAPTCHA composition +`8c9712b7bda0c7515568c08bae710be4bea675a1`, containing dev `32149ee`. +The two production email redemption handlers were extracted without changing +their behavior, then exercised through owned HTTP and real in-memory SQLite. +`npm run test -w @graphdone/server -- tests/auth-link-boundary.test.ts +--maxWorkers=1 --minWorkers=1` failed both negative controls: a reset link +produced a login JWT, and a login link completed password reset with HTTP200. +No token values or deployed credentials are included in this record. + +Cause: both routes consumed the same untyped token, and changing a password +changed neither existing JWT authority nor other outstanding links. A standalone +select/consume followed by password update cannot supply the desired reset +atomicity. Carry purpose, account identity and revision in the issuance record, +and evaluate them with execution-time expiry in the conditional SQLite write. +Increment the account revision with the password and revoke links through a +trigger in the same statement. Compare JWT revision with the current active +account on HTTP and WebSocket boundaries; never refresh a stale authorization +snapshot into the new revision. + +The implementation and migration contract are in +[the authentication lifecycle document](../../docs/quality/AUTH_LINK_LIFECYCLE.md). +Legacy untyped links are invalidated; old signed JWTs map to revision zero and +remain revocable. The existing inactive-account test exposed +`SQLITE_ERROR: no such column: deactivationDate` when it first exercised the +real status method instead of directly changing a row. The migration now adds +that column too, and status mutation can return an inactive profile without +making it authenticatable. + +Intermediate corrections retained in local logs: the new User type initially +missed the bootstrap admin's revision; the old inactive-account fixture issued +a link after disabling the user, which is now rejected at issuance; async +WebCrypto proof solving exceeded a five-second case budget. The test now solves +the same real 100,000-bound SHA-256 challenge with Node's synchronous hash loop, +keeping challenge policy and assertion deadlines unchanged. The signing-key +environment import remains before auth modules after route extraction. + +At the first composed token snapshot, typecheck/lint passed and the full server +suite passed **150/150**, including10 HTTP/SQLite/WebSocket lifecycle cases, +11 existing magic-link cases and9 startup cases. Subsequent strengthened +wrong-purpose/correct-use assertions and inactive request handling require +fresh focused validation; final exact-head review and CI results belong in the +PR discussion. Local logs under `/tmp/graphdone-resolution-20261027/token-*` +retain the initial failures and corrections; the repository record travels with +the fix rather than treating those local files as published knowledge. + +Limits: no live provider/email/Cloud/D1 acceptance or deployment is established. +Existing requests authorized before reset are not rolled back. Idle sockets +are denied at their next operation/result, not claimed to be instantly closed +across API processes. Do not infer verified-provider email or account-linking +safety from token-purpose tests; that remains separate work in #184. + +Independent review of `b79ecd593ef92a12e9d4b3070da68e42f9254477` found two +additional live consumers: human JWTs in `agent-api.ts` and `operations-api.ts` +still used signature extraction without checking the account revision. On +2026-10-27, four real-router negative controls all failed with HTTP201 instead +of401: reset and disable/reactivate sessions could mint new scoped credentials +in each API. The agent route used a bounded graph adapter, while both auth and +operations stores used real SQLite. Signature verification is not session +authorization; enumerate every live token consumer when adding revocation. +Both human paths now call the common active-account/revision resolver. Existing +scoped credentials keep their explicit separate contract. The corrected +16-case HTTP/SQLite/WebSocket suite passed; full frozen-head checks and review +follow in the PR. The operations smoke script also now signs explicit revisions. diff --git a/.agents/inbox/2026-10-27-explicit-camera-input-must-stop-auto-fit.md b/.agents/inbox/2026-10-27-explicit-camera-input-must-stop-auto-fit.md new file mode 100644 index 00000000..1af569ef --- /dev/null +++ b/.agents/inbox/2026-10-27-explicit-camera-input-must-stop-auto-fit.md @@ -0,0 +1,49 @@ +# Explicit camera input must stop pending automatic framing + +On 2026-10-27, [PR #192 CI run 40567753567](https://github.com/GraphDone/GraphDone-Core/actions/runs/40567753567) +tested feature head `74c122d17897d61272e056df02a7957a686f2e2a` on GitHub's merge +composition `f7dd01a57766ff1919237c9852cabfd5130d6eac`. Fixture setup no longer +exhausted the interaction timeout, but both semantic LOD cases failed their +first requested camera scale: expected 0.5, observed 0.1379447032 at 1440px +and 0.0720887246 at 390px. The basic smoke sequence passed 6/6; new-user had +42 passes, two LOD failures and the harness's failed-process evidence row. +This is a distinct application race, not the earlier fixture-budget failure. + +## Cause and corrective rule + +Initial auto-fit samples the settling layout every 250ms. Canvas pointer/wheel +input stopped that loop, but the minimap's explicit camera functions only set +`cameraManuallyPositionedRef`. Because the minimap is outside the canvas, a +pending fit could overwrite its requested zoom. Overview inspection had the +same omission. Explicit pan, zoom and item inspection now mark the current +graph as framed; direct D3 source events do so too. Programmatic automatic +fit/restore still does not claim manual intent. The existing graph ID guard +keeps this ownership scoped to the current graph. + +The first warm local negative control passed, showing that ordinary timing +was insufficient to reproduce the CI race reliably. The browser regression +now pauses the browser clock before graph loading, advances through the first +fit at 350ms, requests the minimap zoom, then advances six 250ms intervals +while checking the camera remains at 0.5. It resumes real time for the remaining +mouse/touch/navigation and persistence checks. It does not extend timeouts, +add retries, wait for auto-fit to finish, or bypass the production camera path. + +With the strengthened test, restoring the unmodified component from +`74c122d` caused the desktop case to fail at the camera-stability assertion; +restoring the fix passed the same case. Both ordinary desktop/phone cases +also passed before clock control. Commands used Node 22.18.0, an owned +Neo4j/API/Vite stack, and headed Playwright with one worker and zero retries. +Final two-viewport, related camera/resize/detail and CI results are recorded in +the PR discussion; this lesson alone does not claim those pending gates pass. + +Limits: these checks cover camera intent and immutable stored positions on the +301-item fixture. They do not establish full multilevel semantic projection, +1k/10k performance, hosted behavior or deployed-source identity. Preserve the +original failed CI artifacts and the warm negative-control pass alongside the +deterministic failure; a later green run must not erase either observation. + +Independent review identified a lost initial-fit geometry sample after moving +zoom earlier. The test now checks font size, viewport bounds and card overlap +both at the first automatic fit and after explicit zoom, retaining both samples +in its geometry attachment. Clock control keeps the first sample from waiting +out the pending fit loop. diff --git a/.agents/inbox/2026-10-27-federation-consumers-bound-uuid-upgrades.md b/.agents/inbox/2026-10-27-federation-consumers-bound-uuid-upgrades.md new file mode 100644 index 00000000..eec78399 --- /dev/null +++ b/.agents/inbox/2026-10-27-federation-consumers-bound-uuid-upgrades.md @@ -0,0 +1,79 @@ +# Validate federation consumers before replacing their UUID dependency + +On 2026-10-27, work for #186 started from dev +`ef495eff15ed7aac51e72dc7a1db1bb96d2b3695` with Node 22.18.0/npm 10.8.2. +`npm explain uuid` identified Core's direct UUID 9.0.1 and +`@neo4j/graphql@5.12.15 → @apollo/subgraph@2.14.4 → +@apollo/federation-internals@2.14.4 → uuid@9.0.1`. +Server UUID 11.1.1 and Mermaid UUID 14.0.3 were separate, already patched paths. + +A bounded direct call on the baseline gave the same result for UUID v3 and v5: +a one-byte output buffer was modified and no error was thrown. The advisory +[GHSA-w5hq-g745-h8pq](https://github.com/advisories/GHSA-w5hq-g745-h8pq) +identifies 11.1.1 as patched. Inspected application calls use unbuffered v4; +federation's `src/operations.ts` uses unbuffered v1 for deferred-directive keys. +This source observation does not establish safety for future callers. + +## Cause and correction + +Updating subgraph to 2.15.1 removed the old federation dependency, but a real +`Neo4jGraphQL.getSubgraphSchema()` probe with a federation v2.3 `@link` failed: +`TypeError: doc.definitions is not iterable` in `concatAST`. The installed +subgraph's `modulesFromSDL` no longer normalizes Neo4j's single +`{ typeDefs, resolvers }` input. The initial probe also omitted `@link`; that +fixture error was corrected before observing this compatibility failure. + +Retain subgraph/federation 2.14.4. Upgrade Core directly to UUID ^11.1.1 and +scope the transitive override to `@apollo/federation-internals@2.14.4` only. +UUID 11 retains CommonJS support; UUID 12 removes it. The existing server UUID +copy deduplicates to 11.1.1; Mermaid remains 14.0.3. No other package version +changes are part of this fix. + +Adding an override and running `npm install --package-lock-only` initially left +the old root UUID resolution. The lock and buffer regression failed, exposing +that stale result. `npm update uuid --package-lock-only --legacy-peer-deps` +then `npm ci --legacy-peer-deps --no-audit --no-fund` installed 1,220 packages; +`npm ls uuid @apollo/subgraph --all` confirmed the intended consumer paths. +Do not infer the installed result from an override declaration alone. + +## Evidence and limits + +`node --test tests/lib/dependencies/uuid.test.mjs`: **4 passed**, covering every +locked UUID, actual consumer-relative CommonJS and ESM imports, valid generated +IDs, invalid v3/v5/v6 buffer bounds without partial writes, and real Neo4j +federation schema generation. An attempted `_service` execution without a driver +was a fixture error; schema generation/printing needs no database. Database-backed +execution remains the separate real-Neo4j contract gate. + +A fresh-cache `npm audit --omit=dev --json` after the correction reported +**2 moderate router entries and 1 high Undici entry**, with no UUID/federation +finding. Undici is separately addressed by #194; that audit preceded composition with +merged dev #194. This count is dated dependency metadata, not a live exposure or +full-security claim. Complete runtime, browser and final CI results belong in +the associated PR discussion; no merge/deployment is established here. + +Corrective rule: upgrade the narrowest compatible consumer chain, keep the +broken-upstream evidence, test both module systems and the actual schema API, +and verify the resolved lock and installed paths after a clean install. Revisit +the version-scoped override when federation changes; do not widen it silently. + +## Review and composed validation + +Independent review found that a simple `>=11.1.1` lock guard would wrongly +accept affected 12.0.0 and 13.0.0. The regression now uses the complete advisory +union and affected/fixed controls on every listed major, including malformed +versions and an unfixed prerelease. The selected 11.1.1/14.0.3 versions were +already patched; the correction prevents a future false green. + +Composition `aba24a34d193b60e2318cd5b197f66e64f320215` incorporates reviewed dev +`32149ee61ea5436831d95d76e4c4b6dc2db619a3`. A clean install and fresh installed +production audit reported **2 moderate router entries and 0 high/critical**. +All four package unit suites passed with one task/worker at a time, retaining +their original timeouts, and the composed harness passed 167/167 before the +additional advisory-range control. An earlier concurrent web run hit the +existing five-second save-test deadline under host load; its single-worker +rerun passed 326/326. The following earlier harness ran while composition was +changed and is discarded as provenance, not counted as validation. Freeze the +source/dependencies throughout a run and use bounded local concurrency. The +final advisory-test change requires its own harness run and exact-head CI; +results are recorded in the PR discussion. No failed run was erased. diff --git a/.agents/inbox/2026-10-27-github-connector-write-permissions.md b/.agents/inbox/2026-10-27-github-connector-write-permissions.md new file mode 100644 index 00000000..e2685ccb --- /dev/null +++ b/.agents/inbox/2026-10-27-github-connector-write-permissions.md @@ -0,0 +1,38 @@ +# A GitHub connector denial describes that client, not every configured client + +Date: 2026-10-27. Context: publishing the owner-authorized GraphDone LOD work as +a draft PR to `dev`. Searched the symptom index and the existing live-ref/transport +lesson before recording this distinct create-PR case. + +## Evidence + +The feature branch push succeeded through the existing SSH remote. The GitHub +connector's `create_pull_request` then returned: + +```text +HTTP 403: Resource not accessible by integration +``` + +The existing CLI returned `mvalancy` for `gh api user --jq .login` and successfully +ran `gh pr create --repo GraphDone/GraphDone-Core --base dev --head +feature/dynamic-lod-overview-20261027 --draft --body-file `. +Readback confirmed [PR #192](https://github.com/GraphDone/GraphDone-Core/pull/192) +was open, draft, and pointed at the intended source head. No authentication, +account, token scope or repository protection was changed. No merge occurred. + +## Cause and corrective rule + +The integration could read this repository but could not create the PR. Its +permission boundary differs from the already configured CLI credential. Report +that specific limitation rather than declaring all GitHub access blocked or +requesting broader credentials. For an already-authorized operation, an existing +client with its own valid access can be used; verify the resulting object and +source head. Do not use another client to evade repository policy, an account +denial, an approval rejection or an operation outside the user's authorization. + +## Limits + +This establishes one successful draft-PR creation through existing credentials. +It does not establish write access for other repositories, non-author approval, +merge readiness or deployment authority. The narrower admin-integration playbook +still governs merges; this incident does not amend that policy. diff --git a/.agents/inbox/2026-10-27-live-graph-review-can-attempt-layout-writes.md b/.agents/inbox/2026-10-27-live-graph-review-can-attempt-layout-writes.md new file mode 100644 index 00000000..3a63b287 --- /dev/null +++ b/.agents/inbox/2026-10-27-live-graph-review-can-attempt-layout-writes.md @@ -0,0 +1,72 @@ +# Viewing a live graph can attempt layout writes even without dragging + +Date: 2026-10-27 UTC. Searched the current performance-evidence index and the +October 24 semantic-review worktree's symptom index, write-it-down guidance and +read-only capture lessons before recording this finding. + +## Evidence + +The existing harness at `01f0c4df1ba82eaaca9769d366886c76b99f0e6b` ran: + +```text +TEST_URL=http://127.0.0.1:8540 GRAPH_REVIEW_NAME='' \ +OPERATOR_CREDENTIAL_FILE= \ +PLAYWRIGHT_CHANNEL=chrome \ +GRAPH_REVIEW_OUT=test-artifacts/graph-lod-review/continuation-20261027 \ +GRAPH_REVIEW_SNAPSHOT=1 npm run report:graph-lod + +finishedAt=2026-10-27T14:56:23.996Z +nodes=301 edges=370 captures=18 pageErrors=[] +blockedWrites=4 (POST /api/graphql) +1 failed: Review attempted a server write; request was blocked, inspect caller +``` + +No drag, seed or graph-edit interaction was requested. Node/edge identity +assertions passed. Desktop and phone fit screenshots still show no readable +titles or relationships. Served asset: `/assets/index-1788931697531.js`; its +exact source SHA is not established by the harness revision. + +A second bounded diagnostic installed API interception before login, permitting +queries and only the login mutation. It selected the same graph, waited, resized +to phone and fit the camera. It printed only operation/field names: + +```text +blocked=4 +operation=UpdateWorkItems +rootField=updateWorkItems +variableKeys=[where,update] +updateFields=[positionX,positionY] +``` + +That diagnostic exited 143 after printing; do not record it as a passing test. +The original sweep exited 1. Both failures remain explicit. Private screenshots +and the snapshot stay in the prior worktree's ignored artifacts directory. + +## Cause and corrective rule + +The application attempted automatic position persistence during a visual review. +Source has a `simulation.on('end.persist', ...)` → `persistAllPositions()` path; +the exact browser caller and affected graph were not captured, so that path is +a hypothesis, not a proved request stack. Do not attribute the request to zoom +alone or assume the selected graph was the mutation target. + +Install the read-only guard before application navigation, allowing only the +intended authentication exchange and reads on the configured API origins. +Retain safe operation/field names for rejected calls, never tokens, payload values +or session state. An attempted write remains a failed review even when blocked. +Reproduce on an owned fixture before correcting persistence intent. Camera, LOD +expansion and temporary summary coordinates must not enqueue saved positions. + +## Limits and regression path + +The first sweep installs its guard after login, so its blocked-write evidence +does not prove there were no earlier writes. Identity-set equality does not prove +position equality or whole-graph immutability. The diagnostic blocked the four +observed position updates, but did not establish root-cause completion. + +`tests/lib/graphReview.test.mjs` in the prior review branch passed 2/2 guard tests; +the real sweep supplies a failing acceptance case. LOD-0 in the +[continuation plan](../../docs/architecture/dynamic-graph-lod.md) ports and extends +that harness on current dev, with owned layout-persistence reproduction and +independent coordinate readback. No production fix or new passing regression is +claimed by this documentation-only change. diff --git a/.agents/inbox/2026-10-27-lod-projections-need-renderer-ownership.md b/.agents/inbox/2026-10-27-lod-projections-need-renderer-ownership.md new file mode 100644 index 00000000..4c8f3331 --- /dev/null +++ b/.agents/inbox/2026-10-27-lod-projections-need-renderer-ownership.md @@ -0,0 +1,66 @@ +# Display projections need explicit renderer ownership and real selection checks + +Date: 2026-10-27. Repository: GraphDone-Core. Base: current dev +`ef495eff15ed7aac51e72dc7a1db1bb96d2b3695`. Related implementation: +`semanticLOD.ts`, `SemanticGraphOverview.tsx`, `InteractiveGraphVisualization.tsx`. +Existing guidance searched: symptom index, performance-evidence index, the live +graph write-attempt lesson and the snapshot-merge lesson. + +## Evidence and cause + +The first nine projection/placement tests passed, but independent review found +that a sibling summary overlay bypassed the existing SVG's native gestures and +overlapped Workspace controls. Moving it inside the retained SVG fixed gesture +ownership, but exposed a second boundary: descendant `svg.select('defs')` could +select React's nested definitions instead of the source renderer's definitions. +Source children also override inherited visibility/pointer-events, so hiding only +their parent with those properties did not reliably disable source hit targets. + +The second headed browser run used an owned Neo4j fixture, 301 pinned items and +370 relationships, at widths 1440 and 390: + +```text +TEST_URL=http://127.0.0.1:28528 QC_BROWSER_PORT=28529 xvfb-run --auto-servernum \ + npx playwright test tests/e2e/graph/semantic-lod.spec.ts \ + --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --reporter=line +2 failed: getByTestId('node-inspector') expected visible; element(s) not found +``` + +The item camera moved, but the member click removed its browser section before +the document outside-click handler inspected the target. A `closest()` exclusion +alone failed because the target's detached subtree no longer reached the overview +root. Stop click propagation at the React overview boundary, while allowing native +camera gestures to reach the owning SVG. The third run passed both viewports, +including inspector selection, Back, wheel, zero attempted writes and exact saved +position readback before/after navigation and reload. + +Earlier validation failures are retained in ignored +`test-artifacts/lod-evidence/first-attempt/` and `second-attempt/`. The first run +also had fixture confounders: a concurrent typecheck rebuilt Core and restarted +the watched API; 100-input Neo4j create mutations exceeded the test timeout. +Use the built API without a watcher and repeated 20-input mutation shapes for +this fixture. Keep the full 301/370 dataset. The default parallel web unit run +had a 16ms timing assertion fail and a 5s UI timeout; a one-worker run passed all +335 tests. Do not run timing-sensitive validation alongside heavy fixture work. + +## Corrective rule + +- Keep source and projected objects separate; projected coordinates never enter + simulation merges or the save queue. +- Give React and D3 explicit sibling roots. Scope D3 definition selection to + direct children and preserve the React root during initialization. +- Hide source groups with `display:none` while the overview owns interaction; + refresh text fitting once they become measurable again. +- Exercise ordinary pointer, keyboard and touch navigation through the actual + production component. Assert the inspector and focus, not only camera motion + or a `data-active` flag. Check forced Overview at normal item scale too. +- Bind Back state to an inspection session, including graph changes, and retain + exact typed/directed edge accounting even when some labels cannot fit. + +## Limits + +These are local owned-fixture results, not evidence of deployment or Cloud parity. +This slice retains the full source SVG and full graph queries. It does not resolve +legacy autosave of newly placed/overlapping nodes, nested spatial cuts, streaming, +or the 1k/10k performance targets. A local note is not published knowledge until +the related reviewed change is integrated. diff --git a/.agents/inbox/2026-10-27-lod-reuse-needs-live-consumer-evidence.md b/.agents/inbox/2026-10-27-lod-reuse-needs-live-consumer-evidence.md new file mode 100644 index 00000000..1e5f7906 --- /dev/null +++ b/.agents/inbox/2026-10-27-lod-reuse-needs-live-consumer-evidence.md @@ -0,0 +1,49 @@ +# Reuse decisions need consumer evidence as well as a passing library test + +Date: 2026-10-27 UTC. Searched GraphDone's semantic/performance lessons and +CodeGraph's current unified-viewer plan before updating the LOD continuation. + +The owner identified prior CodeGraph LOD work omitted from the first GraphDone +plan. Review found useful implemented behavior in the mandala and modern +continuous Structure workspace, plus a tempting standalone LOD module whose +comments describe broader sharing than its consumers establish. + +## Receipt + +At the reviewed CodeGraph source revision: + +```text +node tools/codegraph/src/codegraph/assets/lib/lod.js --test +lod.js self-test: OK + +rg -n 'lib/lod|window\.LOD|LOD\.(swap|band|hysteresis|labelAlpha|fadeIn)' \ + tools/codegraph/src/codegraph/assets +.../assets/lib/lod.js:72:if (typeof window !== "undefined") window.LOD = LOD; + +tools/codegraph/docs/UNIFIED-VIEWER-PLAN.md: +records that lod.js has no viewer consumers and enlighten uses inline LOD + +git diff --name-only -- tools/codegraph + +``` + +The active inline hierarchy walk retains a parent when estimated children would +be too small; `placeLabels` prioritizes folders and collision-culls names. +`structure-workspace.js` separately maintains stable page/world identity, +bounded requests and generation checks. Those are the stronger reuse references. +Some legacy browser controls still expect mandala globals at the new Structure +front door. The installed `/api/runtime` returned 503; no live viewer pass or +performance guarantee was established in this review. + +## Cause, corrective rule and limits + +The first plan considered GraphDone in isolation. The follow-up also illustrates +why a file's name/header and passing pure tests do not establish runtime adoption. +Before calling code shared, trace imports and actual consumers, then verify the +behavior contract in each. Reuse observed algorithms and test invariants first; +extract a minimal common module only with two wired consumers and an appropriate +licensing boundary. Do not transfer historical FPS claims or stale test routes. + +Canonical guidance: [CodeGraph reuse map](../../docs/architecture/codegraph-lod-reuse.md). +This change updates the plan and its discovery links. It copies no proprietary +source, changes no renderer, and does not claim the integration is implemented. diff --git a/.agents/inbox/2026-10-27-lod-title-refresh-must-not-own-camera.md b/.agents/inbox/2026-10-27-lod-title-refresh-must-not-own-camera.md new file mode 100644 index 00000000..b0ad43ee --- /dev/null +++ b/.agents/inbox/2026-10-27-lod-title-refresh-must-not-own-camera.md @@ -0,0 +1,65 @@ +# LOD title refresh must not interrupt the owning camera transition + +Date: 2026-10-27. Issue #205. The symptom index already covered explicit camera +input cancelling automatic framing and renderer ownership; neither addressed an +overview-exit title refresh interrupting the active user animation. + +## Evidence + +CSS PR #199 CI run `40591493014` failed the desktop semantic-LOD case at camera +scale `1.1735656098434863` while expecting `1.2`. Its retained trace shows this +was after the test clock resumed; it was not an unadvanced-clock failure. + +A new production-renderer regression starts at `1.1`, requests the existing +120 ms minimap zoom to `1.2`, and advances twelve 16 ms frames. It explicitly +observes overview exit above `1.15` before the target is reached. On the original +runtime, `npm run qc:lod -- --grep 1440 --trace=retain-on-failure` failed with +`1.1767031700493764` instead of `1.2`. The trace is retained under the owned +`/tmp/graphdone-resolution-20261027/lod-transition-negative-artifacts` directory. + +The corrected runtime passes both 301-node/370-edge desktop and phone LOD +journeys in 3.3 minutes with one worker and zero retries. Existing membership, +label geometry, selected-item navigation, graph switching and zero-position-write +checks remain. Build, typecheck and lint pass (existing lint warnings remain); +web tests pass 346/346 and the harness passes 175/175. Exact-head review and CI +remain required before integration; source-only evidence is not a live deployment. + +## Cause and corrective rule + +The overview-exit effect reapplied the current camera through +`zoomBehavior.transform` to make formerly hidden title elements measurable. +D3's selection form of that API interrupts the selection's active transition. +Thus a presentation refresh cancelled the same animation that crossed the LOD +threshold, saving an intermediate camera position. + +Refresh source-title geometry directly using the live camera scale after source +elements become visible. Scope the selection to the source `.main-graph-group`, retain +the same-graph transition guard, and respect simplified rendering. Do not emit a +camera command merely to refresh presentation. Camera transitions belong to the +navigation operation that initiated them. + +## A live renderer selector needs its own observation + +Follow-up review found the initial title-only change used `.main-group`, while +both renderer initializers create `.main-graph-group`. The effect was therefore +a no-op. Animated zoom still passed because later frames refitted the titles. +An initial fixed-camera geometry-only check also passed; neither result proved +the explicit refresh actually ran. These passing-but-insufficient controls are +retained rather than presented as a negative control. + +The strengthened fixed-camera Overview→Items phase instruments the selected +real SVG title's native `getBBox` method, retaining its actual geometry result. +It requires a visible measurement, bounded title geometry, an identical camera +and zero minimap camera publications, with the animation clock paused. The +incorrect selector fails with zero visible measurements. With the actual source +selector, both desktop and phone cases pass in 4.2 minutes with zero retries; +the source selector +must be taken from the actual renderer. Do not infer presentation work occurred +merely because an animation reached its target. + +## Limits + +This corrects overview-to-item transition ownership; it does not implement the +remaining multilevel grouping, bounded 1k rendering or progressive 10k loading +milestones. The browser regression uses owned synthetic graphs and preserves the +original interaction deadlines, required assertions and strict fixture cleanup. diff --git a/.agents/inbox/2026-10-27-motion-sampling-needs-camera-readiness.md b/.agents/inbox/2026-10-27-motion-sampling-needs-camera-readiness.md new file mode 100644 index 00000000..fbc26e74 --- /dev/null +++ b/.agents/inbox/2026-10-27-motion-sampling-needs-camera-readiness.md @@ -0,0 +1,48 @@ +# Idle motion sampling must wait for completed edits and camera resize + +Date: 2026-10-27. Issue #208. The symptom index already covers explicit camera +ownership and LOD transition interruption, but neither establishes when a rotated +viewport is ready for an idle-motion measurement. + +## Evidence + +OAuth PR #202 CI run `40597251568` failed the phone motion case with a horizontal +span of `185.395922` pixels against the unchanged one-pixel bound. Its retained +trace and 1,021 frame samples show one common camera transform change at sample +index 2, followed by 1,019 identical frames. Source-node coordinates stayed +unchanged. Sampling began about 6.8 ms after viewport resizing returned, before +the queued ResizeObserver/frame callback completed. The preceding type edit was +also still pending. This evidence identifies an unfinished setup transition, +not sustained idle physics motion. + +The renderer now exposes `cameraReady` alongside the existing physics `atRest` +metric. Readiness requires the measured camera viewport to match the current +container, no queued resize frame, and no active SVG camera transition. The +existing resize behavior and physics definition are preserved. + +The browser case verifies the edit through an independent API read and rendered +type badge. It deliberately holds real graph ResizeObserver delivery during +phone rotation and proves `atRest` can be true while `cameraReady` is false. +Readiness must remain false after observer delivery until its frame is processed. +Only then does the unchanged full 17-second motion sample begin. + +On the prior runtime the new contract fails because `cameraReady` is absent; +`motion-readiness-negative.log` and its trace are retained in the owned dated +evidence directory. This local negative is a missing-contract test, not a claimed +reproduction of the original CI jump. With the correction, all three viewports +pass in 2.0 minutes, with zero retries. Build, typecheck, lint and harness 175/175 +pass; existing warnings remain. Exact-head independent review and CI are required. + +## Cause and corrective rule + +Physics rest does not imply completion of asynchronous edits, ResizeObserver +delivery or camera framing. Observe each boundary before measuring idle behavior. +Keep the actual sample duration, per-frame observations, node count and one-pixel +limits; do not hide setup races by widening the motion threshold or retrying. + +## Limits + +`cameraReady` describes the current viewport, resize frame and SVG transition. +It does not promise that no future timer or user action will move the camera. +The test separately observes persistence and rendered edit completion. It uses +owned synthetic graph fixtures and strict cleanup, not production graph writes. diff --git a/.agents/inbox/2026-10-27-oauth-linking-needs-provider-proof-and-atomicity.md b/.agents/inbox/2026-10-27-oauth-linking-needs-provider-proof-and-atomicity.md new file mode 100644 index 00000000..87f06c08 --- /dev/null +++ b/.agents/inbox/2026-10-27-oauth-linking-needs-provider-proof-and-atomicity.md @@ -0,0 +1,74 @@ +# OAuth email linking needs provider proof and an isolated transaction + +On 2026-10-27, the #184 OAuth review traced live `oauth-strategies.ts` into +`findOrCreateUserFromOAuth` at token head `5aa307c`. Synthetic owned SQLite +controls established two failures: unverified email linked to the existing +admin fixture, and an intentionally rejected provider insert left one new user +instead of zero. No live provider or shared user database was touched. + +## Evidence and correction + +`npm test --workspace @graphdone/server -- tests/oauth-identity.test.ts` on the +unchanged implementation produced two failures and one disabled-subject control +pass after correcting the fixture's initially wrong status-method name. The +first run's fixture error is not evidence of an application defect. + +The fixed helper requires explicit verified-email/authority evidence for new +links, selects bound provider+subject before email, rejects inactive accounts, +and checks the authenticated revision again. Dedicated file-backed SQLite +transactions make user creation and provider linking atomic. Actual provider +strategy tests exposed important adapter contracts: GitHub's default mapping +drops verification flags; LinkedIn's short callback skips userinfo. The revised +configuration retains GitHub metadata, requests LinkedIn userinfo with the +nine-argument callback, checks its subject and uses its documented issuer. +Google third-party email verification alone is not ownership authority. + +The first concurrent transaction implementation used native busy waits on eight +connections; the five-second regression timed out. SQLite waits can occupy the +native worker pool needed by the transaction holding the lock. Serialize this +process's OAuth writes by database file, keep the transaction on its own +connection, hash before acquiring the write lock, and use bounded asynchronous +busy retries. Do not put an async transaction on the shared auth connection: +unrelated writes could join its rollback. The regression now proves eight first +logins create one user/binding, and an unrelated rollback cannot erase that user. +No test timeout increase was used as the correction. + +Node 22.18.0 focused validation passed 38 cases across `oauth-identity`, +`oauth-profiles` and `oauth-strategies`, including actual local HTTP callback +issuance and stale-revision rejection. Initial build errors from the GitHub +state declaration, an unused token import and the project's augmented Express +request type were corrected without weakening runtime checks. The callback +uses contextual `RequestHandler` typing. Final full gates are recorded on the PR. + +## Limits and recovery + +The [provider contract and recovery policy](../../docs/quality/OAUTH_IDENTITY.md) +links official sources and describes existing bindings, absent proof, legacy +credential retention and synthetic-test limits. Existing bindings are preserved, +not retrospectively certified. New provider tokens/raw profiles are not stored; +callback logs no longer expose credential-bearing redirects. Private traces and +raw tokens must stay out of repository notes. No deployed provider acceptance +or hosted account clearance follows from these local tests. + +## Independent review corrections + +Review of `c4561ae` found a preregistration takeover path: an existing unverified +local account kept its earlier password and JWT when verified OAuth linked to +its email. The added negative uses the actual password-login resolver and +current-account JWT resolver before attempting the link; it failed against +`c4561ae` because the link succeeded. The corrected transaction rejects an +unverified existing email match. A separate verified-account positive remains. +No account transfer or credential reset is silently performed. + +The review also drove the actual OIDC strategy with asynchronous provider +callbacks. Malformed userinfo JSON reached an installed library catch block +that calls undefined `done`, causing an uncaught ReferenceError. GitHub's email +loader also parses JSON outside a safe error boundary. The final implementation +disables those library profile loaders and fetches their fixed HTTPS endpoints +with bounded time/body size, redirect rejection and explicit JSON/status checks. +It retains the authenticated ID-token subject for LinkedIn and exact GitHub +verification metadata. Asynchronous malformed/error/oversize/abort controls now +fail authentication without account lookup. The synchronous first fixtures had +not exercised this exception path; provider doubles must preserve asynchronous +callback boundaries. The focused corrected set passed 49 cases. Full final +review and gates remain in the PR rather than inferred from this count. diff --git a/.agents/inbox/2026-10-27-reconcile-advisory-backports-with-locked-consumers.md b/.agents/inbox/2026-10-27-reconcile-advisory-backports-with-locked-consumers.md new file mode 100644 index 00000000..13f87d9c --- /dev/null +++ b/.agents/inbox/2026-10-27-reconcile-advisory-backports-with-locked-consumers.md @@ -0,0 +1,34 @@ +# Reconcile updated advisory ranges with the already locked consumer + +The October 23 inventory and #186 recorded three moderate CSS-chain entries. +On 2026-10-27, fresh-cache `npm audit --omit=dev --json` on dev +`8712389e24f567f5b3e6ed132c1ac5d4dafb21fa` reported five moderate entries: +Router/DOM and UUID/federation/subgraph, with no CSS-chain entry. + +The [official selector-parser advisory](https://github.com/advisories/GHSA-rj75-hqrm-r3gf) +was read directly and via `gh api /advisories/GHSA-rj75-hqrm-r3gf`. +Its `updated_at` is `2026-10-26T14:07:57Z`, `withdrawn_at` is null, and its +affected ranges are `<6.1.5` and `>=7.0.0 <7.1.6`, with fixes 6.1.5 and 7.1.6. +`npm ls postcss-selector-parser postcss-nested tailwindcss --all` resolves +Tailwind 3.4.19 and postcss-nested 6.2.0 to parser 6.1.5. This was already locked +in commit `36c02c020832cafd8f67b9fae85d126827e89a98`, dated 2026-10-23: +`git show 36c02c0:package-lock.json` contains the same parser version. + +The historical audit remains evidence of what that run reported. The current +official fixed ranges and actual consumer now establish that this locked CSS +path is patched. There is no dependency update or reclassification in this +follow-up. Do not force a Tailwind major migration or attribute the older audit +to an unproved cache/publication cause merely to make the historical count agree. + +The dedicated regression checks both maintained fixed branches and every locked +parser path, then resolves the actual Tailwind and postcss-nested consumers. +It verifies flat-selector round trips, nested selector output, and generated +base/responsive Tailwind utilities. The flat input is bounded at 2048 classes; +this is semantic coverage, not a claimed CPU-exhaustion benchmark. Fresh build, +integrated browser checks and exact-head results belong in the PR discussion. + +Limits: trusted build-time CSS was the inspected application surface. This is +clearance of the named advisory for the current resolution, not full CSS/npm/OS +security or deployed-site acceptance. Router, UUID and dev-tool work have their +own PRs. Preserve fresh dated audit payloads as metadata can change independently +of the lockfile. Local evidence: `/tmp/graphdone-resolution-20261027/css-*`. diff --git a/.agents/inbox/2026-10-27-router-upgrades-need-current-consumer-contracts.md b/.agents/inbox/2026-10-27-router-upgrades-need-current-consumer-contracts.md new file mode 100644 index 00000000..803c9e0d --- /dev/null +++ b/.agents/inbox/2026-10-27-router-upgrades-need-current-consumer-contracts.md @@ -0,0 +1,87 @@ +# Validate the installed router contract and real navigation controls + +On 2026-10-27, the #186 router work started at dev +`8712389e24f567f5b3e6ed132c1ac5d4dafb21fa`. A fresh-cache production audit +reported five moderate entries: Router/DOM and UUID/federation/subgraph. The +[navigation advisory](https://github.com/advisories/GHSA-wrjc-x8rr-h8h6) and +[hydration advisory](https://github.com/advisories/GHSA-337j-9hxr-rhxg) both +identify 7.18.0 as fixed. The selected Router/DOM 7.18.4 retains React 18 and the +existing declarative BrowserRouter; its Node 20 minimum fits supported Node 22/24. +The [versioned migration guide](https://reactrouter.com/7.18.4/upgrading/v6) +was checked. No multi-segment splat route, data router or component-local lazy +page declaration was found in the live app routes. + +The first navigation test copied an older upstream normalization expectation. +Router 6.30.6 failed three mixed-separator cases; installed 7.18.4 rejected all four +external-looking destinations instead of navigating to a normalized internal +path. That four-case failure was a test-contract mismatch, not an application +regression. Inspection of the installed `validateNavigationTarget` established +the actual rejection boundary. The corrected control captures the thrown error +and requires the route to remain unchanged. Reinstalling the original locked 6.30.6 +then running the final control produced **4 failures and 1 history pass**; +the 7.18.4 control plus existing role guards produced **9 passes**. + +The first six actual-browser cases produced **4 failures and 2 passes**, with no +retries. The two failing assumptions on each viewport were that the sign-in +page always selected Password, and that Admin opened its Users tab. The current +UI preserves the sign-in method and opens Feedback & operations. The corrected +test explicitly selects Password and Users, retains the exact route/content +assertions, and checks that denied users cannot mount the administration heading. +History, lazy-page navigation, viewer guards and fresh-context callback routing +remain required. Final browser and integrated results belong in the PR. + +Corrective rule: use the installed consumer and current UI to establish a +regression contract, then run the exact final negative control against the old +locked consumer. Preserve the earlier failing logs; do not treat a test rewrite +or a successful install as proof that navigation remains usable. The lock +regression checks every Router/DOM resolution, not just the manifest range. + +Limits: MemoryRouter tests establish library navigation behavior, while the +actual-browser suite covers GraphDone routes at 1440px and 390px. No hostile +application-controlled destination, SSR hydration surface, live provider +callback, deployed revision or broad security clearance is claimed. Test tokens +are confined to disposable local/CI accounts; failure traces are not public +credential samples. Local evidence is retained in +`/tmp/graphdone-resolution-20261027/router-*` and this record travels with the fix. + +At frozen `3e5a761dc0b067ed3827dd29fca50d45fb5d3952`, the corrected browser suite +passed 6/6 without retries, the full web suite passed 340/340 and the harness +passed 167/167. Review found the inventory overstated the installed-version guard: +it checked locked ranges and installed-pair equality, which could accept two +equally stale installed copies. The guard now requires each actual web consumer +to satisfy the fixed range and match its resolved lock entry. The inventory +states that exact scope. Refresh the changed harness and final CI before merge. + +## Empty-account CI correction + +Composed Router head `14a4fcc` [CI 40584829418](https://github.com/GraphDone/GraphDone-Core/actions/runs/40584829418) +completed 50 browser cases in 854 seconds: 48 passed, two viewer cases failed, +plus the harness's failed-process row. This was not a process-budget timeout. +Retained screenshots show the authenticated empty workspace at both widths. +The desktop assertion incorrectly required a populated graph's `view-content`; +the phone login helper did not recognize an authenticated account without a +graph selector. The local fixture had graphs and hid both assumptions. + +Use the existing authenticated `workspace-content` container for account-level +route checks and keep login-form exclusion. Router cases additionally assert +`me.role === VIEWER` through the real API before and after the fresh-context +callback. A standalone real-browser control recognizes an empty workspace, +rejects a password form even with workspace markup, and rejects a loading page. +With the old helper, the empty-workspace positive failed deterministically; +this helper correction adds no retry, wait or timeout. Original CI artifacts +remain evidence, and final current-head browser/full CI gates still apply. + +The first empty-workspace/password-form negative used a zero-height empty main; +independent review measured it invisible, so that case could pass for the wrong +reason. It now retains visible content and asserts both the workspace and the +password field are visible before requiring an unauthenticated result. + +Gate capacity is separate from that failure: focus CI's 44-case new-user sequence +passed in 892 seconds, only eight below the 900-second process budget; UUID's +same suite took 768 seconds. Required LOD and route acceptance now run as separate +blocking sequences after the 42-case new-user suite. All 51 cases remain required +(42 existing, two LOD, seven route/helper controls), with unchanged per-action +and per-test deadlines, one worker and zero retries. CI runs and uploads each +result independently; pr/full profiles also include both new sequences. CLI +contract tests execute the enrollment/trace arguments. Do not describe this as +fixing the original two viewer failures or count superseded runs as passes. diff --git a/.agents/inbox/2026-10-27-runtime-images-need-installed-consumer-proof.md b/.agents/inbox/2026-10-27-runtime-images-need-installed-consumer-proof.md new file mode 100644 index 00000000..d5bb704c --- /dev/null +++ b/.agents/inbox/2026-10-27-runtime-images-need-installed-consumer-proof.md @@ -0,0 +1,46 @@ +# Runtime pruning needs actual consumer and persistence evidence + +Date: 2026-10-27. Issue #185. Searches of the symptom/security indices found +consumer-verified dependency and startup lessons, but no runtime-pruning contract. + +## Evidence + +`docker run --rm --entrypoint node -i graphdone-audit-patched-api:20261023 +--input-type=module < tests/runtime/image-inventory.mjs` failed on installed +Vite/Vitest/compiler/lint dependencies. A selected production install removed +them, but the first real compiled API launch failed `ERR_MODULE_NOT_FOUND` for +`graphql-tag`. The dependency had been supplied by the excluded web workspace. +Declaring the existing locked version in server restored the real consumer. + +Review also found a remaining npm-based startup shell call, proposed UID drift, +an SDK root entry that is intentionally absent, and auth volumes targeting a +directory different from the process's store. The final source removes the +redundant shell call, preserves runtime identities, validates SDK subpaths and +points production volumes at the actual store. Three final image contracts pass +real API/MCP/database readback and account recovery after container replacement. +Host server 211/211, harness 192/192, types and lint pass; final CI remains required. + +The full Trivy 0.76.1/dated-DB inventories show 0 npm findings in all images, 0 OS +findings in the Alpine variants, and 228 residual Bookworm OS entries after the +tzdata update. A raw critical MiniZip entry has a specific Debian applicability +explanation; this does not justify dismissing the rest. Full provenance, +commands, negative controls and limits are in the +[runtime packaging record](../../docs/quality/RUNTIME_IMAGE_PACKAGING_2026-10-27.md). + +## Cause and corrective rule + +A successful monorepo build can hide undeclared imports supplied by another +workspace. Copying a pruned root node_modules alone can lose nested dependencies, +native binaries, process identity or data ownership. Install selected production +consumers from the lock, preserve workspace-local trees, compile native code +against the runtime libc, and execute the actual compiled consumers. Test the +real data mount across container replacement; in-memory SQLite is insufficient. +Use the image's default production configuration for missing-secret tests. + +## Limits + +Present packages are not necessarily reachable surfaces. Scanner output and +package counts do not establish exploitability or full-image security clearance. +Retain raw findings and use primary distro evidence for scoped applicability. +No deployment, volume migration/deletion, identity change or protection change +is authorized by an image inventory or this lesson. diff --git a/.agents/inbox/2026-10-27-snapshot-merges-are-not-page-appends.md b/.agents/inbox/2026-10-27-snapshot-merges-are-not-page-appends.md new file mode 100644 index 00000000..a48336a1 --- /dev/null +++ b/.agents/inbox/2026-10-27-snapshot-merges-are-not-page-appends.md @@ -0,0 +1,56 @@ +# Snapshot replacement helpers must not consume individual graph pages + +Date: 2026-10-27 UTC. Searched the current symptom/performance indices, +`docs/design/progressive-streaming.md`, and the October semantic-LOD proposal. + +## Evidence + +At current Core dev `ef495eff15ed7aac51e72dc7a1db1bb96d2b3695`: + +```text +packages/web/src/lib/graphDataMerge.ts +mergeSimulationNodes: + incomingIds = new Set(incoming.map(n => n.id)) + removedIds = simNodes.filter(n => !incomingIds.has(n.id)).map(n => n.id) + returned nodes are populated only from incoming +mergeSimulationEdges: + if (!sourceNode || !targetNode) { droppedIds.push(fresh.id); continue; } +``` + +The older streaming design says background pages can flow directly through these +identity-preserving helpers with no change. Source contradicts that assumption: +identity preservation for retained objects is not append semantics. + +The existing focused suites were also run in the prior semantic-review worktree +(`01f0c4d`) on 2026-10-27: + +```text +npm run test -w @graphdone/web -- \ + src/lib/__tests__/edgeLOD.test.ts \ + src/lib/__tests__/parallelEdges.test.ts \ + src/lib/__tests__/graphDataMerge.test.ts \ + src/lib/__tests__/adaptiveQuality.test.ts +4 files passed; 60 tests passed +``` + +The merge suite explicitly tests removed IDs and dropped missing-endpoint edges. +These tests verify the existing full-snapshot contract, not a streaming fix. + +## Cause and corrective rule + +The design conflated preservation of object identity with accumulation of partial +data. Introduce a canonical loaded-data registry with distinct snapshot/page/ +update/tombstone operations. Reconcile a page into that registry first. Pass the +reconciled loaded set to the D3 identity merge. Keep unresolved endpoints in a +pending-edge registry until loaded or explicitly deleted; do not equate unloaded, +filtered and deleted states. Bind pages to graph/access scope and revision. + +## Limits and regression path + +This is a source-level design incompatibility, not a reproduced production +streaming outage: streaming is not implemented. Preserve existing complete- +snapshot deletion semantics. LOD-1/LOD-7 in the +[continuation](../../docs/architecture/dynamic-graph-lod.md) require two-page +accumulation, delayed endpoint, explicit deletion, stale revision and graph/account +switch tests. No new runtime test is needed to claim this plan-only correction; +passing future streaming tests remains an implementation requirement. diff --git a/.agents/inbox/2026-10-27-tool-upgrades-need-executed-consumer-evidence.md b/.agents/inbox/2026-10-27-tool-upgrades-need-executed-consumer-evidence.md new file mode 100644 index 00000000..75c71d8f --- /dev/null +++ b/.agents/inbox/2026-10-27-tool-upgrades-need-executed-consumer-evidence.md @@ -0,0 +1,38 @@ +# A tool's nonzero exit is not proof that its task failed correctly + +Date: 2026-10-27. Issue #185. Existing dependency lessons cover actual consumers +and runner evidence; no indexed lesson distinguished a CLI parsing failure from +an executed task's failure during a major build-tool migration. + +## Evidence + +The first Turbo 2 contract combined `--cache=local:rw` and `--force`. The tool +returned `the argument '--cache' cannot be used with '--force'` before executing +the intended failing task. `remaining-tooling-contract-cli-negative.log` retains +this failure. The draft assertion rejected it; it is not acceptance evidence. + +The corrected contract selects local write-only cache mode for the negative +case, then requires the real child's unique `owned-core-failure-23` marker, +nonzero runner status and no dependent execution. It also proves dependency +ordering, environment passthrough and cache invalidation/reuse. Installed Turbo, +Mermaid/KaTeX and Vite contracts pass 3/3 on Node 22.18 and 24.22.1. + +Separately, old installed KaTeX 0.16.47 fails an inert inherited-trust expression +that the patched consumer rejects. Lock inventory alone would not demonstrate +that behavior. The browser test resolves Mermaid from its actual declaring +workspace, including when npm places it outside root `node_modules`. + +## Cause and corrective rule + +Major tools can change CLI semantics, task configuration, environment filtering, +dependency placement and rendering defaults. Verify the installed consumers and +their observable outputs. Require evidence that the intended failure boundary +was actually reached; an arbitrary nonzero process exit is insufficient. Keep +compatibility choices explicit and retain unsuccessful draft evidence. + +## Limits + +These are owned local consumer checks, not production exposure or exploitability +claims. No remote cache login, registry publication or deployment is performed. +The [migration record](../../docs/quality/REMAINING_TOOLING_2026-10-27.md) records +exact versions, primary sources, dated audits and remaining acceptance gates. diff --git a/.agents/inbox/2026-10-27-toolchain-upgrades-must-preserve-test-contracts.md b/.agents/inbox/2026-10-27-toolchain-upgrades-must-preserve-test-contracts.md new file mode 100644 index 00000000..e00e3bc5 --- /dev/null +++ b/.agents/inbox/2026-10-27-toolchain-upgrades-must-preserve-test-contracts.md @@ -0,0 +1,56 @@ +# Toolchain upgrades must preserve discovery, coverage and consumer behavior + +Date: 2026-10-27. Issue #185; source based on reviewed auth stack `d83fd9a`. +Searches of the symptom/security indices found dependency-scope and consumer +lessons but no current Vite/Vitest migration procedure. + +## Evidence + +The initial Vite 7.3.7 / Vitest 4.1.12 build failed DOM matcher types. A nested +runner layout prevented the matcher augmentation resolving its consumer. The +supported Vitest matcher import and deduplication fixed the build. Unrelated +lint patch drift was restored rather than accepted as part of the upgrade. + +`npm run test` initially reported 237 server cases rather than 211: Vitest 4 +also discovered the built copies of 26 source tests. The explicit dist exclusion +restored exactly the tracked source file set. Discovery with and without build +outputs is identical: core 3, server 16, MCP 11, web 45. Coverage explicitly keeps +unimported source and the executable offline-shell plugin at zero hits. + +Node 22 and 24 coverage exposed expensive editor queries and asynchronous test +proof solving. The five-second editor and 20-second CAPTCHA deadlines were +retained. Direct accessible-label queries preserve all editor assertions; a +native SHA-256 search still solves the actual signed 100,000-bound challenge. +A local CPU-protected database run failed six cases on the busy host; a separate +CI-equivalent run passed all 136, without claiming production CPU acceptance. +All earlier failures remain under the dated evidence directory on the test host. + +Current harness: 192/192; actual unified web adapter: 353/353. An owned browser +contract exercises real HMR/state, HTTP/WebSocket proxying, secret denial and +compiled offline loading. Supported-Node coverage is a required CI matrix check. +See [the migration evidence and limits](../../docs/quality/TOOLCHAIN_MIGRATION_2026-10-27.md) +for commands, versions, authoritative references and remaining work. + +`node --test tests/lib/adapters/execution-evidence.test.mjs` initially failed +the new optional-skip case: real Vitest 4 reports an explicitly skipped assertion +inside `numPendingTests`. The adapter now reconciles that count against actual +skipped assertions while still rejecting unfinished pending assertions, required +skips and counter contradictions. Real installed-runner controls separately +reported pass, failure, required-skip failure and optional skip, with zero +passes for either skip. The complete harness then passed 192/192. + +## Cause and corrective rule + +A successful install or lower audit count does not preserve runner behavior. +Upgrade runner, coverage, transformer and Vite as compatible consumers. Compare +actual source discovery, inspect zero-hit coverage, exercise real reporter output +through the strict harness, and test worker/database and browser behavior. Never +count compiled duplicates, skipped required cases or earlier failed attempts as +passes. Do not replace timeouts with retries to mask migration failures. + +## Limits + +This is a toolchain slice, not runtime-image security clearance. Turbo and +Mermaid/KaTeX remain separately scoped; image packaging/scanning, hosted acceptance, +main promotion and deployment are unfinished. Native browser/database checks +used owned Linux fixtures. Final exact-head review and CI remain required. diff --git a/.agents/inbox/2026-10-27-widget-events-need-commit-phase-listeners.md b/.agents/inbox/2026-10-27-widget-events-need-commit-phase-listeners.md new file mode 100644 index 00000000..ffe3447a --- /dev/null +++ b/.agents/inbox/2026-10-27-widget-events-need-commit-phase-listeners.md @@ -0,0 +1,52 @@ +# Attach verification listeners before a committed widget can emit + +Date: 2026-10-27. + +## Evidence + +Token head `017a509443ead9b58febc940b4b63ed4aab70a97`, CI +[40591509438](https://github.com/GraphDone/GraphDone-Core/actions/runs/40591509438), +failed the web CAPTCHA case: expected the latest verified callback to contain +`issued-proof`, received an empty string. The DOM-presence assertion had passed. +The log does not itself record React phase ordering. + +The deterministic regression wraps the actual component in `React.Profiler`. +Its commit callback dispatches one verification event for each newly observed +widget, after child layout effects and before passive effects. Against the old +component, `npm exec --workspace @graphdone/web -- vitest run src/components/AuthCaptcha.test.tsx` +failed the new case while four existing controls passed. Changing only the +listener/reset block to `useLayoutEffect` passed all five. An independent probe +reproduced the same before/after outcome. + +A first local unbounded full-suite run also retained an unrelated 5-second +WorkItemDetailsModal timeout and its subsequent save-control failure. With the +same action deadlines and bounded workers (`--maxWorkers=2 --minWorkers=1`), +the complete web suite passed 353/353. This is explicitly different execution +configuration, not a silent retry or proof of that timeout's precise cause. + +## Cause and corrective rule + +A committed custom element can be visible before a passive effect installs its +event listener. An immediately emitted proof can therefore be lost. Attach and +clean up the verification listener in the layout phase; policy fetching and +focus behavior remain passive. Preserve failure, expiry and replacement resets. +See [React layout effect timing](https://react.dev/reference/react/useLayoutEffect). + +Test the early event through actual React commit ordering. Retain controls that +replacement widgets deliver events and removed, prior-action and unmounted +widgets cannot deliver stale proofs. Do not fix this with an added test wait, +retry, or longer timeout. No authentication policy or proof validation changes. + +## Limits + +The regression proves this lifecycle hazard, not the exact CI scheduling trace. +It does not certify arbitrary custom elements that synchronously emit from a +constructor or connectedCallback before React attaches refs. Installed ALTCHA +initialization yields asynchronously; actual production-mode browser acceptance +and final exact-head CI remain separate gates. + +The corrected source passed build, types, lint and 179/179 harness checks. +The rebuilt SPA against its owned production-mode API passed all four +`qc:captcha` browser cases in 17.8 seconds with one worker and zero retries: +real proof/wrong-password/retry at 390px and 1440px, direct forged/absent REST +and GraphQL rejection, and pending/failed/expired submission blocking. diff --git a/.agents/inbox/2026-10-28-advisories-follow-installed-consumers.md b/.agents/inbox/2026-10-28-advisories-follow-installed-consumers.md new file mode 100644 index 00000000..b924fe4d --- /dev/null +++ b/.agents/inbox/2026-10-28-advisories-follow-installed-consumers.md @@ -0,0 +1,64 @@ +# Fresh advisories require consumer evidence even when application code is unchanged + +Date: 2026-10-28. Searched the security symptom index and existing dependency-scope, +backport and installed-consumer lessons. Issue227 is the bounded follow-up to +PR226's required audit failure; the UI correction is tracked separately in225. + +## Evidence and cause + +`gh run view 40784339385 --repo GraphDone/GraphDone-Core --log-failed` reported +one high graphql-scalars finding and one moderate @hono/node-server finding, +while the previous bcf source's audit had passed. Both advisories were added to +GitHub's database on2026-10-28. This does not mean this PR introduced the code. + +- [GHSA-x8rm-f576-gq67](https://github.com/advisories/GHSA-x8rm-f576-gq67) + describes PhoneNumber regular-expression denial of service. The locked + graphql-scalars1.24.2 was a direct server dependency with no source import, + no other incoming lock dependency and no declared PhoneNumber schema use. + Remove that unused dependency. Do not upgrade an unused package across a + major solely to clear an audit. The actual Neo4j schema supplies its DateTime + scalar without it; this is separately exercised after removal. +- [GHSA-rmxm-3fg6-px4f](https://github.com/advisories/GHSA-rmxm-3fg6-px4f) + affects the installed MCP SDK's node-server2.1.1 adapter. The SDK's existing + range admits the fixed2.1.x line. A targeted lock update resolves2.1.5; + upstream2.1.3 fixes double decoding,2.1.4 admits Hono5 peers and2.1.5 fixes + HTTP/2 cancellation and restricts static serving to GET/HEAD. The installed + Hono4 and supported Node22/24 contracts remain in range. No override is added. + +`npm update @hono/node-server --package-lock-only --ignore-scripts --no-audit` +also normalized unrelated peer/dev flags. Those unrelated entries were restored; +an executable before/after comparison found exactly three changed lock entries: +node_modules/@hono/node-server, removal of node_modules/graphql-scalars, and the +server workspace's removed dependency. No other resolved package changed. + +## Corrective rule and executed regression + +Inspect the consumer's actual module resolution, not only the top-level version. +The new dependency control resolves the Hono adapter through the installed MCP +SDK and sends requests through its static middleware using owned temporary files. +An ordinary encoded-space filename remains readable, explicit protected paths +remain denied, and the malformed second-decode path cannot expose the fixture. +The original2.1.1 fixture fails with200 instead of404; the corrected2.1.5 fixture +passes. POST cannot return the static file. No production service is probed. + +Local commands on2026-10-28: + +- Node22 `npm ci --ignore-scripts --no-audit --no-fund`: successful in the owned + dependency worktree. Native install hooks are deliberately not covered locally. +- `npm audit --omit=dev --json --cache `: zero findings in every + severity, with the corrected lock. This is production scope at that audit time. +- `node --test --test-concurrency=1 tests/lib/dependencies/hono-static.test.mjs`: + corrected3/3 passed in0.684s. Old installed/locked fixture:2fail/1pass in0.718s; + both the version guard and actual malformed-path request discriminated it. +- `npm ls graphql-scalars @hono/node-server --all`: only SDK1.32.2 → + @hono/node-server2.1.5 remained; no graphql-scalars copy was installed. + +## Limits + +The protected-file control is an isolated dependency contract, not proof that +GraphDone exposes static private files through Hono. No reachable PhoneNumber +field or deployed exploit was established. The existing server schema, real +Neo4j/MCP and all runtime image gates still must pass on the final composition. +A local install with skipped hooks does not prove native runtime packaging. +Keep this failed audit and subsequent exact-source results attached to227; +close only after reviewed dev integration and actual post-dev acceptance. diff --git a/.agents/inbox/2026-10-28-agent-setup-needs-separate-task-regions.md b/.agents/inbox/2026-10-28-agent-setup-needs-separate-task-regions.md new file mode 100644 index 00000000..2cd18172 --- /dev/null +++ b/.agents/inbox/2026-10-28-agent-setup-needs-separate-task-regions.md @@ -0,0 +1,19 @@ +# Agent setup needs separate task regions and honest access availability + +On 2026-10-28, the owner reported that Your agent lacked structure. Headed inspection of dev `00f0211` showed API limits above the first action, followed by token setup, saved access and notes in one undifferentiated column. At phone width the native permission option clipped its explanation. The same component also appears inside the narrower Agents-page column. + +## Evidence + +- The revised component separates connection setup, project access and shared notes, with collapsed API/sharing details below. Desktop uses two columns; narrow/embedded surfaces retain one. Permission names are short, with complete explanations outside the native select. +- `TEST_URL= npx playwright test tests/e2e/quality/agent-collaboration.spec.ts --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 --trace=retain-on-failure`: first run 1 passed / 2 failed in 53.63s. Both failures were stale mobile navigation expectations: current mobile/short-landscape entry is More → Connect an agent to this project. No timeout/retry/assertion was relaxed. +- After using the actual entry, 3 cases passed in 32.66s at 390×844, 844×390 and 1440×900. The journeys create owned project-scoped credentials, exchange human/agent notes through the actual API, check read-only enforcement and revocation, and inspect the embedded component. Geometry receipts and screenshots are file-backed attachments; secrets are hidden/masked and owned credentials revoked during cleanup. +- Focused component checks pass 2/2: visible loading and retry after a failed read without access creation; local notes remain usable when token access cannot be fetched, without claiming the token list is empty. +- The existing collaboration spec was not enrolled in the blocking PR matrix. It now has its own bounded serial `agent-connection` sequence, preserving existing sequences. CLI enrollment/failure-evidence controls pass 37/37. + +## Cause and corrective rule + +A single long form concealed distinct jobs and assumed the viewport width also described the embedded container. Give primary jobs their own headings and boundaries; use readable controls, reserve space for explanations, and keep technical reference secondary. Check the actual phone navigation and embedded placement, not only desktop screenshots. Failed/unavailable reads must not masquerade as an empty successful result. A refresh action should update notes without requiring more background polling. + +## Limits + +Local visual evidence uses owned Core fixtures, not hosted D1. Component tests model offline availability but do not establish a production offline synchronization contract. The explicit real-inference opt-in remains separate; these API journeys spend no inference credits. Full final-source hosted gates and independent review remain required for integration. diff --git a/.agents/inbox/2026-10-28-async-errors-must-retain-account-ownership.md b/.agents/inbox/2026-10-28-async-errors-must-retain-account-ownership.md new file mode 100644 index 00000000..20090fe5 --- /dev/null +++ b/.agents/inbox/2026-10-28-async-errors-must-retain-account-ownership.md @@ -0,0 +1,46 @@ +# Async errors must retain account ownership instead of relabeling newer state + +Date: 2026-10-28 UTC. Independent review of layout candidate +`c4df01e1567af10b6c4b0dc9d666ad946c1e0b75` found a Settings disclosure race. +The existing receipt-ordering lesson covered database writes; this incident +concerns the owner of already loaded UI state on an asynchronous error path. + +## Evidence + +The retained owned probe and unit regression ran with Node 22.18.0: + +```sh +node /tmp/graphdone-resolution-20261027/layout-settings-review-negative/probe.cjs +npx vitest run src/hooks/__tests__/useLayoutRecovery.test.ts --root packages/web +``` + +A starts a discard while its account read is held. B signs in and B's pending +graph list loads. A's discard rejects after the held read returns. The old catch +spreads the newest state, including B's scopes, and overwrites its owner with A. +Returning to A before its next read resolves printed: + +```text +visibleToA [{"graphId":"b-private-graph","count":1}] +error The signed-in account changed. No further layout changes were discarded. +``` + +`layout-settings-negative-unit.log` retains the failing hook test. After the +correction, `layout-settings-corrected-unit.log` reports 43 passed across seven +focused suites, including the new delayed account-switch regression. + +## Cause and corrective rule + +An owner label is provenance, not a presentation field to replace on failure. +Associate operations with the current owner scope identity, ignore stale +completion/error paths, and retain prior scopes only if their recorded owner +matches. Scope busy state too, so a previous owner's completion cannot clear a +new owner's pending operation. Number refresh requests to ignore older reads. +Test errors after a replacement account's data has loaded, and inspect the +first render after returning before the fresh read completes. + +## Limits + +The probe and unit test use a controlled account store. They prove the UI +interleaving, not hosted authorization or backend isolation. Existing real +browser account-switch and deleted-graph regressions remain required, as do +renewed exact-head review and strict CI before integration. diff --git a/.agents/inbox/2026-10-28-browser-acceptance-needs-an-owned-session-bus.md b/.agents/inbox/2026-10-28-browser-acceptance-needs-an-owned-session-bus.md new file mode 100644 index 00000000..dcac8fbd --- /dev/null +++ b/.agents/inbox/2026-10-28-browser-acceptance-needs-an-owned-session-bus.md @@ -0,0 +1,91 @@ +# Browser acceptance needs an owned session bus when the desktop bus disconnects + +Observed 2026-10-28 UTC during the frozen nested LOD candidate validation. A +search of `.agents/`, browser helpers and Playwright configuration found no +existing D-Bus disconnection lesson. This is an infrastructure failure, not a +passing browser run or evidence of an application regression. + +## Evidence + +```text +2026-10-28 +xvfb-run --auto-servernum node tests/run-unified.mjs --sequence layout-recovery +source: a311154bf154af977a238d4b5de55773efa4e2dc, clean and unchanged +result: 8 passed, 1 browser case failed, 1 execution-evidence failure, exit 1 +Chrome: FATAL:dbus/bus.cc:1245] D-Bus connection was disconnected. Aborting. +case: revoked graph writes and deleted graphs retain scoped recovery +``` + +Retained report/trace: `/tmp/graphdone-resolution-20261027/nested-layout-a311154-owned-env/`. +The invoking process inherited a session bus address under `/run/user/`; that bus +path existed during the subsequent check. The evidence does not identify what +disconnected the bus, whether the session or system connection caused the fatal +error, or another process responsible. Do not invent an attribution. + +A previous run at the same source failed the explicit owned-Neo4j credential +precondition before that case performed mutations. Its eight passes and failed +precondition remain in `nested-layout-a311154/`. Supplying only the three owned +Neo4j variables corrected that setup error; it did not excuse this later abort. + +## Corrective rule + +Keep the source and index frozen, preserve the failed trace, and run the browser +under `dbus-run-session -- xvfb-run --auto-servernum ...` with the same cases, +assertions, budgets and zero retries. This gives the invocation its own session +bus without changing the user's services or killing unrelated processes. Use +only the owned database credentials and retain strict teardown. The isolated +run still must pass; launching it alone is not acceptance. + +## Avoid desktop service activation in the owned bus + +The default private-session run also failed and remains retained in +`nested-layout-a311154-private-bus/`: eight cases passed; the owned Chrome +SIGKILL/restart case exceeded its unchanged 90s budget. Its two first app +navigations took 26.353s and 27.587s. The log records desktop portal/secret-service +activation reaching the default 120s service-start timeout. Review confirmed the +portal was initially requested by ordinary Playwright Chrome, which already uses +isolated password-store flags. These correlated observations do not prove the +raw helper's missing flags caused the delays. + +The installed `dbus-daemon(1)` documentation and `/usr/share/dbus-1/session.conf` +show that service directories enable automatic desktop-service activation. A +narrower invocation uses a temporary private config with a Unix socket, EXTERNAL +authentication, session type and same-user messaging policy, with no service +directories or inherited configuration. It changes no desktop/system service. +Retained `browser-session-bus.conf` and `browser-session-bus-preflight.log` show +`ListActivatableNames` returning only `org.freedesktop.DBus`. The strict command +adds `--config-file=/tmp/graphdone-resolution-20261027/browser-session-bus.conf` +to `dbus-run-session`; all application/test source, cases, limits and retries +remain unchanged. Actual resulting acceptance remains required. + +## Limits + +This local invocation adjustment does not prove the desktop bus root cause or +prevent every system-bus/Chrome crash. It does not change CI policy or justify +retrying unexplained application failures into a pass. No application code or +browser timeout was changed for this incident. + +## Owner-directed workstation limits + +On 2026-10-28 the owner reported a window-manager crash and requested small visual +checks without overloading the local PC. The minimal-bus run was interrupted at +that point: 7 cases passed (including owned SIGKILL/restart and revoked/deleted +access), 2 required cases skipped, and execution evidence failed. It remains +incomplete at `nested-layout-a311154-minimal-bus/`; neither the earlier remote CI +pass nor the seven local cases turn that interruption into a local suite pass. + +The host snapshot showed 45 GiB used RAM, 13 GiB swap used and load averages +13.59/23.09/44.31. This is observation after the report, not proof that GraphDone +or another process caused the desktop crash. The agent stopped its active test +process group, 14 verified owned API/Vite/preview listeners and 3 owned containers. +The owner's localhost 8540 and unrelated processes were left untouched. Retained +fixtures/data and negative artifacts were not deleted. + +Use small deterministic geometry tests and static SVG rendering for iteration. +Run scoped component checks serially at low priority, with one worker; no browser +or database is needed for the basic wire-clutter gate. Put full build/database/ +browser/image matrices in CI. Bring up only the specific owned preview needed +for a bounded inspection, then stop it. Do not accumulate obsolete previews or +turn each uncertain timeout into another local full-suite run. Geometry metrics +must include exact relationship accounting and useful visible content so hiding +all wires cannot manufacture a clean result. diff --git a/.agents/inbox/2026-10-28-browser-evidence-needs-files.md b/.agents/inbox/2026-10-28-browser-evidence-needs-files.md new file mode 100644 index 00000000..6e699248 --- /dev/null +++ b/.agents/inbox/2026-10-28-browser-evidence-needs-files.md @@ -0,0 +1,51 @@ +# Browser evidence must exist as files before report collection + +2026-10-28, PR #224. Independent review comment4404454674 verified that +`runPlaywrightSequence` ignores attachments without a path. The new priority +journeys used `info.attach({body})`; the actual Playwright JSON normalizer retained +body/no path, and the real adapter probe returned a passing case with zero +attachments and zero errors. The517742c2,000-item stage completed successfully +in CI40742884920 at18:22:57UTC, but this did not establish retained priority +accounting evidence. Preserve that distinction and the earlier report. + +Cause: producer-side inline attachments did not meet the existing unified +collector's file-path contract. A green browser assertion alone does not prove +that another machine can inspect its measurements or screenshots. + +Corrective rule: write JSON via `info.outputPath` and `writeFile`, save screenshots +to output paths, then attach by `path`. Required evidence must survive the actual +unified report and uploaded archive, not only Playwright's in-memory report. +Do not weaken owned-path checks or silently call missing evidence accepted. + +The corrected1440px headed priority journey passed in13.09s with one worker and +zero retries. Its actual JSON report lists file attachments for +`priority-accounting.json` (3193 bytes) and `priority-fallback.png` (61545 bytes), +and both files exist. Retained local report: `priority-artifacts6.json` in the +owned resolution evidence directory. The2,000-item case now writes and attaches +`two-thousand-priority.json` by the same existing pattern; it is not rerun locally +under the workstation resource constraint. + +Limits: this is a correction to the new test producers, not a claim that all +legacy inline attachments are collected. Final exact-source CI must execute the +large case and independently verify its retained counts, screenshots, artifact +paths and source fingerprint. No runtime or policy behavior changed. + +Independent inspection of the materialized 9b8409c image found a second evidence +gap: the screenshot showed an almost empty canvas after reload/inspection, even +though canonical fallback IDs were correct. The test now waits for camera +readiness, frames the owned pair, and requires its actual painted stroke and +visible “Depends On +1” label. It uses the shared symbol oracle, including useful +visible titles, minimum text/wire weight, and label/card/wire clearance. + +The first strengthened run (`priority-artifacts7`) rejected a painted horizontal +SVG path because Playwright's area-based visibility predicate sees zero height. +The replacement checks ancestor visibility, actual stroke/opacity/length and +viewport intersection. The next run (`priority-artifacts8`) correctly rejected +the 390px scene: its horizontal endpoint titles were both off-screen. The phone +fixture now positions the same owned Gateway/Worker pair vertically before the +API baseline is recorded. No assertion or source-position invariant was relaxed. +Both desktop 1440 and phone 390 journeys passed in 25.17s in +`priority-artifacts9`, with actual geometry JSON, PNG and accounting files. The +phone image shows both endpoint titles and the fallback connection; the desktop +image shows four cards. All failed reports/images remain retained. This remains +a test/evidence correction; no product defect was established by these failures. diff --git a/.agents/inbox/2026-10-28-browser-sequences-need-bounded-feature-scope.md b/.agents/inbox/2026-10-28-browser-sequences-need-bounded-feature-scope.md new file mode 100644 index 00000000..5eca38db --- /dev/null +++ b/.agents/inbox/2026-10-28-browser-sequences-need-bounded-feature-scope.md @@ -0,0 +1,52 @@ +# Growing acceptance coverage needs bounded feature sequences + +Date: 2026-10-28. Searched the symptom index, cold-fixture budget and browser +file-evidence lessons before changing the PR226 harness enrollment. + +## Evidence and cause + +`gh run view 40780316041 --repo GraphDone/GraphDone-Core --log-failed` +reported `failed — 0✓ 1✗ 0⚠ 0⏭ (901.0s)` for the new-user sequence onbcf3946. +The actual strict report has exitCode:null, timedOut:true, no valid Playwright +JSON, and only the synthetic execution-evidence failure. Earlier completed +new-user durations were662.050s on the accepted224 checkpoint and709.295s on3ec, +whose actual desktop control overflow was independently fixed inbcf. + +The retained smoke-layout trace began at885.829s process time. The outer runner +killed it at899.782s, about14s into its60s test, during an existing wait. Its +closed-page/channel errors followed shutdown. This is an aggregate900s runner +limit, not proof that the layout interaction independently timed out. No retry +or green predecessor can turn this incomplete run into acceptance. + +Eight raw inspector PNGs and two receipts survived and were inspected: both +widths recorded zero mode overflow,96px title bounds and no writes/page errors. +They are not completed-case evidence because the final Playwright report is +missing. The receipts contain no elapsed timestamps, and extracted file mtimes +are download times; do not invent an inspector duration from them. Downstream +semantic/nested/recovery/routes/mobile/living/performance stages did not run. + +## Corrective rule and regression + +Keep the original42-case new-user membership intact and move the new two-case +inspector feature into its own blocking `inspector-readability` sequence. Enroll +it explicitly in the serial CI Smoke Gate and both pr/full profiles. Preserve +every browser assertion, all per-test and adapter timeout values, one headed +worker, zero retries, failure tracing and strict owned-fixture cleanup. A feature +sequence has its own complete report and attachments; a missing report still fails. + +The CLI regression verifies separate enrollment, exact inspector spec membership, +headed single-worker execution, no retries and failure-only tracing. A deliberate +failed Playwright result must fail this sequence and retain its trace and JSON +even after the original output directory is removed. The original new-user +sequence retains the same negative control. This is harness isolation, not a +renderer performance improvement or reduced acceptance coverage. + +Both new sequences and the entire final CI composition must pass with valid +reports before integration. Runtime and inspector source bytes are unchanged +by this correction; hosted validation remains required for the workflow change. + +Local validation on2026-10-28: Node22 serial CLI and execution-evidence suites +passed75/75 in10.47s, with zero skips. An executable comparison against accepted +dev showed every original sequence definition and profile membership unchanged; +only inspector-readability was added. `git diff --check` passed. These harness +controls do not substitute for the fresh hosted browser reports. diff --git a/.agents/inbox/2026-10-28-bus-views-need-live-membership-and-total-budgets.md b/.agents/inbox/2026-10-28-bus-views-need-live-membership-and-total-budgets.md new file mode 100644 index 00000000..e4e27fee --- /dev/null +++ b/.agents/inbox/2026-10-28-bus-views-need-live-membership-and-total-budgets.md @@ -0,0 +1,76 @@ +# Bus views need live group membership and one total routing budget + +Observed 2026-10-28 UTC during independent review of schematic candidate +`a04fa2b9dfa580a4fe6c344cc00aa32bd6c8b83e`. Existing projection/ownership and +snapshot-merge lessons were searched through the symptom index first. + +## Evidence + +```text +2026-10-28, Node 22.18.0, single-worker scoped tests +Before: schematic-review-component-negative.log + 6 passed, 4 failed + open internal view retained a member moved to another group + new group member missing from the already-open internal view + open bundle retained old endpoint membership + Connections count omitted internal and unresolved relationships +After: schematic-review-component-final.log + 22 passed, 0 failed, no skips; 5.53s + +Before: schematic-budget-negative.log + 6 passed, 1 failed + with one allowed attempt, an impossible first route did not prevent + a later successful search: expected zero routes, received one +After: schematic-budget-final.log + 8 passed, 0 failed, no skips; 0.24s + failed searches spend the invocation allowance + a one-state allowance stops remaining searches and retains every edge ID +``` + +Logs are retained under `/tmp/graphdone-resolution-20261027/`. The corrected web +typecheck passed in the existing owned scoped probe (`schematic-review-typecheck.log`). +No browser or database was started locally for these corrections. + +## Cause and corrective rule + +A named group is a live semantic selection, not a frozen list of member IDs. +Store a stable hierarchy reference and whether it selects direct members only; +resolve current hierarchy paths whenever source data changes. Derive current +headings from that hierarchy. Explain when a selected group disappears. Test +moves, newly added members, both endpoints of bundles and direct-parent subsets. + +A cap on successful routes does not bound failed searches. Count attempts and +examined search states across the entire invocation, including failed paths. +Stop further work when either allowance is exhausted and retain every remaining +source edge in the explicit deferred partition. The current hard ceilings are +32 attempts (12 on narrow views), 6,000 examined states and a 4,000-point grid; +callers may lower them, not raise them. Those are deterministic operation limits, +not a frame-time or 1k/10k capacity guarantee. Publish actual work counts with the +small visual fixtures. The dense 112-edge fixture now routes 33 edges through 13 +branches and explicitly defers 79; all IDs remain accounted for. + +Counts must describe the whole partition they name. The Connections control now +includes internal, unresolved-endpoint and deferred external relationships in +its not-drawn total and explicitly names each denominator. Internal links do +not need synthetic canvas loops to make that accounting truthful. + +## Rendered geometry is evidence too + +Actual CI 40629282719 failed its new schematic visual sequence: 3 cases passed; +phone failed because Delivery's label ended at 259.265625 where the 8px-padding +boundary was 259. The additional execution failure makes the strict sequence +failed. Other runtime/quality success and Comprehensive 40629282743 do not erase +that failure. Retained artifact 12790822057 includes the screenshot and trace. + +The source SVG generator now wraps at a narrower word boundary. The label's font, +card size, padding assertion, timeout and retry policy are unchanged. Regenerated +static pictures are review aids; only the next actual four-case CI run can prove +this correction against the browser font metrics. Never widen the visual +threshold to turn an observed overlap/padding failure green. + +## Limits + +These fixes do not complete focused-bus exploration or item-level wiring. Small +geometry/component results do not establish full browser/backend acceptance, +large-data loading or release readiness. Keep heavy local matrices stopped under +the owner's workstation constraint and require reviewed exact-source CI. diff --git a/.agents/inbox/2026-10-28-clean-layout-needs-clearance-and-topology.md b/.agents/inbox/2026-10-28-clean-layout-needs-clearance-and-topology.md new file mode 100644 index 00000000..85b34c81 --- /dev/null +++ b/.agents/inbox/2026-10-28-clean-layout-needs-clearance-and-topology.md @@ -0,0 +1,79 @@ +# Clean layout needs measured clearance and topology, not only nonintersection + +On2026-10-28 the owner rejected the headed overview/detailed scenes as a junk pile. Labels and pointer hit areas had improved, but overview cards still used16/24px gutters, ports8px from cards and auxiliary lanes6px apart. A no-intersection metric could therefore pass an unreadable view. Detailed items began from a jittered grid or optional dependency rows, which did not place a connected hub at the center of its neighborhood. + +## Evidence and corrective rule + +The initial source audit identified `overviewSlots`, `ports` and the two-mode layout in `InteractiveGraphVisualization`. The correction uses64px overview gutters,20px unrelated-card routing clearance and16px separation between unrelated buses. `overviewOrganization.test.ts` retains a negative with two noncrossing wires6px apart and only4px from a card. It must fail the spacing contract even though exact intersection counts are zero. Positive real-router fixtures must also draw useful routes and conserve every original relationship. + +The new pure organizer splits connected components, deduplicates parallel relationships for degree, places hubs centrally with radial neighbors, follows chains/cycles, and bounds adjacent-ring refinement to40,000 edge evaluations. Its first2k/5k fixture accidentally repeated the same degree-two edges, so it never exercised refinement; replacing those with a backbone plus distinct chords covers the intended network branch. Four topology tests plus existing LOD/component cases passed29/29 in2.69s; three overview spacing/actual-router cases passed3/3 in0.81s. Web types and scoped new-file lint pass. The touched legacy renderer/workspace lint reports0errors and316 warnings in the touched legacy files; that is not a warning-free claim. + +Detailed organization is explicit and persists through the existing bounded save queue. A completely unplaced scene receives topology placement initially; saved scene positions remain authoritative. Organization does not run on pointer/zoom frames. Overview placement uses the represented relationship projection and bounded slot swaps; a dominant hub retains its central slot. + +## Limits and pending acceptance + +Tracked by #233/PR234. Actual headed control/save/reload, final hosted strict screenshots/clearance receipts, independent review and post-dev acceptance are pending. The original localhost graph is preserved; use disposable owned fixtures. Large-data loading, detailed obstacle-aware item routing (#220), hosted parity and release readiness remain separate. CodeGraph ideas are adapted without shared packages or CodeGraph edits. Passing placement geometry does not prove every detailed edge is readable. + +The first actual organizer journey timed out before exercising organization: it looked for the LOD Detail selector on a nine-item graph, but that control intentionally mounts only at40items. The small fixture now asserts its absence and uses the directly rendered detailed view. The failed run/trace is retained; this is a fixture correction, not evidence that the organizer worked. + +The corrected first actual desktop/phone organization/save/reload journey passed2/2 in25.85s, but visual inspection rejected its phone result: fitting all five cards produced short-word fragmentation in narrow compact boxes. This exposed an insufficient acceptance condition (world-space separation and exact persistence alone). The organize action now requests a readable phone zoom floor, and the fixture measures actual projected card width and complete title words after camera paint/settle. Its first stronger probe caught67px before the requested animation; the next check must wait for that specific visible state and verify it survives save/status changes. This final phone correction is still pending; the earlier25.85s run is not final visual acceptance. + +The existing one-time `separateCards` repair remains an exception for already overlapping saved coordinates: it can move those cards and save the repair on load. This predates the topology organizer. Already-clear saved arrangements remain unchanged until an explicit organization action. The original localhost graph is not a fixture. + +## Lifecycle and camera regressions found during review + +Independent review of `2d81ac0` reproduced two lifecycle defects: rebuilding at low zoom could truncate the simulation to viewport-visible items, and a persisted Hierarchical mode could overwrite explicit organization. The correction keeps complete simulation membership (existing paint culling remains), exits the old layout mode on organization, synchronizes its control and checks the current mode again inside the delayed callback. Do not make a display projection the authoritative item set for layout or persistence. + +The first zoom-floor correction referenced a later-declared graph ID during render and hit the error boundary. It now resets against the existing layout scope. A low-zoom native compact-card title edit followed by organization now proves that an offscreen item remains present and receives its saved position. A separate delayed-edge test proves that fresh zero-position items wait for the authoritative connection query before initial organization; the earlier source-only hypothesis omitted the actual loading guard and was not a verified defect. + +The first control-aware camera scorer moved the hub far off-center (desktop 410px, phone 107px) to reduce obstacle overlap, even hiding neighbors. Keep the horizontal hub central and rank fully visible cards before obstacle area, then distance from the preferred center. Bound the vertical adjustment and candidate work. A desktop negative with an unavoidable large obstruction rejects the old strategy of hiding cards. Readability uses at least 0.65 scale after organization; explicit Zoom to fit can still show the whole board. On a phone the readable central neighborhood requires panning to side neighbors rather than shrinking all text. + +Dated validation, 2026-10-29 UTC, Node22 with one headed worker and zero retries: + +- `npm test --workspace @graphdone/web -- src/lib/organizationCamera.test.ts src/lib/topologyOrganization.test.ts src/lib/overviewOrganization.test.ts`: 9/9 in 1.82s, including the 2,000-item/5,000-relationship bounded-work case. +- `npm run typecheck --workspace @graphdone/web`: passed. +- Web-scoped `npx eslint src/lib/organizationCamera.ts src/lib/organizationCamera.test.ts`: passed. A prior root-scoped invocation failed to resolve the legacy root ESLint config; that was not a source lint pass. +- `npx playwright test tests/e2e/graph/topology-organization.spec.ts --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 --trace=retain-on-failure`: 4/4 in 42.9s. Covers actual desktop/phone central rings, complete short titles and projected width, full-card/control clearance after acknowledged save, exact original relations and saved reload, low-zoom/offscreen title editing, and fresh unplaced data with delayed edges. Desktop retains all nine readable cards; phone retains the central hub and vertical neighbors with side-neighborhood panning. Final geometry/identity JSON and before/after PNGs are file-backed attachments. + +Earlier failed traces are retained separately. In particular, the temporary multi-line save panel covered a phone neighbor during upload; final visual assertions now wait for the actual acknowledged save status, while API persistence assertions remain independent. This does not establish that transient/error overlays always avoid every item, or that detailed item routes avoid all cards. Full final-source hosted gates and independent review remain required. The owned preview included the independently reviewed PR232 bus interactions; final branch composition must include that now-merged source before acceptance. + + +## Human usability review tightened the acceptance contract + +The first phone acceptance permitted three complete cards from a five-item system. Independent visual review correctly rejected the two clipped peers: tiny graphs need complete context. The organizer now evaluates a bounded set of ring angles/aspects against measured rectangular clearance and available phone space, keeping the four-peer arrangement symmetric. A pure five-card fixture checks readable projected extent, centrality,64px world clearance and deterministic reorder. This uses GraphDone's own card geometry; no shared library. + +Reload checks also needed to wait for the debounced camera write and actual camera framing, then repeat the full visible-scene contract. A desktop native probe found late camera drift because explicit organization marked the camera manual without stopping the existing initial-fit poll; it now marks that graph framed just like the other explicit camera actions. The first stronger reload PNGs still captured entrance opacity: bounds were correct while the hub was invisible. Final checks poll actual effective node/title opacity and keep a separate painted reload PNG. An unrelated earlier 97763da CI failed the new empty catch under `no-empty`; its correction reports a storage warning, and full touched-renderer lint has0errors/317 existing warnings. + +The focus card now clears obstacles before maximizing complete neighbor count. A helper-only tall-overlay negative proves that the old count-first scorer could leave the hub fully covered; this was not claimed as a native UI reproduction. Ordinary transient upload progress is excluded from permanent organization framing so its shrinking panel cannot push the finished ring into top controls. Error/attention panels retain obstacle status and recovery actions; four save-status unit cases pass in1.02s. + +On2026-10-29, `topology-organization.spec.ts` with one headed worker/zero retries passes4/4 in45.7s after these changes. It requires all nine desktop/all five phone cards, complete title words, actual paint,12px screen/control margins, readable hub width, original relationships, acknowledged API positions, stable saved/reloaded screen bounds (within0.5px), offscreen edits and delayed fresh edges. Eleven focused topology/overview/camera units pass in1.59s, web types pass, and full touched-file lint passes with the legacy warnings explicit. Fresh PNG/JSON pairs are retained separately from every failed probe. Hosted exact-source acceptance and independent review are still pending. + +Evidence boundary correction: the earlier301-item51.2s journey exercised the new spacing runtime and interaction/conservation, but did not call the quantitative `inspectSchematicOverview` clearance helper. Only the nested and large-symbol journeys did. Do not describe that old301 run as measured64/20/16 clearance acceptance. The next overview chunk adds explicit external shown/total counts, coverage receipts and at-rest quantitative checks to the301 journey. + +## Refinement must preserve the placement invariant + +Independent review of8516bc5 found a new compact-phone defect: initial placement used each card's rectangle, but subsequent edge-length swaps assumed interchangeable slots. A six-item/nine-link dimension fixture reduced minimum clearance to9px. A minimal five-item fixture using actual `graphItemLayout` with its shipped fallback text measurement reproduced64.000027px before refinement and9.000027px afterward; the long nested cards are320×174. This is a verified pure production-function defect, not a claim about a native browser scene. + +The retained six-item regression fails against8516 (five pass, one fail). The correction checks both moved rectangles against every component card before accepting a compact-ring swap, including the hub. It counts/reports rectangle evaluations and rejected swaps and caps rectangle work at40,000, independently of the existing40,000 edge-evaluation cap; the compact search itself is limited to eight peers. Actual measured-title/nested-card and heterogeneous-dimension controls both retain at least64px after refinement. `npm test --workspace @graphdone/web -- src/lib/topologyOrganization.test.ts`:7/7 in0.813s on2026-10-29 UTC. Preserve spacing as a hard invariant throughout optimization; a shorter wire is not improvement if it makes cards crowd or overlap. + +## Readability receipts must show the coverage tradeoff + +Independent usability review found the old “269 not drawn” label mixed internal links with external deferral and could imply that “Links: All” meant every external wire was painted. The visible dock now reports drawn/eligible external connections separately from inside-group links and offers Browse all with the full original count. Coverage receipts retain external/pair denominators, deferred/priority/internal/pending partitions and drawn connected-group counts. No universal high coverage percentage is asserted; an empty view still cannot pass. + +The first added301 clearance call used `.edges` on the old item-only snapshot and failed before the new helper executed. It now uses the separately queried original edge list. The corrected desktop/phone301 journey passes2/2 in~90s with one headed worker/zero retries. Unlike the earlier51.2s case, this executes quantitative clearance checks and retains at-rest PNG/geometry before input inspection. Desktop:361 external,101 drawn,260 deferred,9 internal,38 eligible directed pairs/13 drawn, all8 displayed groups connected by drawn routes; minimum card/wire clearance20/24px and zero card intersections/unrelated crossings/overlaps/diagonals. Phone:2 external connections drawn from361,1/38 pairs,both displayed groups connected; card clearance127px and unrelated-wire clearance null because there is only one bus. Full directed originals remain in Browse. Component/overview units pass12/12 in2.37s; web types and touched coverage-file lint pass. Independent visual review accepts the readability and truthful accounting; these fixtures do not establish that every subset meets every task. + +That review also identified avoidable phone blank space: the fixed160px grid phase can discard a valid preceding row rather than shifting the grid below controls. The current two-card phone view is not an intrinsic capacity limit. Improving obstacle-aware slot offsets remains the next local spatial correction before final acceptance. + +## Measured controls should shift the grid, not waste a readable row + +The retained phone obstacle fixture fails against0b6b532 with two slots where three fit. The slot calculation now evaluates at most32 deterministic vertical phases derived from actual control boundaries, preserving the existing centered columns,96px card height,64px gutters and20px obstacle clearance. Placement and admission capacity use the same selected phase; tie cases retain the existing phase to minimize churn. This is screen-space projection only, with no canonical position write. + +The negative/geometry/component suite passes31/31 in2.45s after the correction. Actual301-item phone journey passes1/1 in40.7s with a single headed worker/zero retries: three complete240×96 cards at y342/502/662 instead of two at492/652,45/361 external originals instead of2/361,4/38 directed pairs instead of1, and all three displayed groups connected by drawn routes. Minimum unrelated-card/wire clearance is20/96px, with zero clutter intersections/overlaps/crossings/diagonals. At-rest PNG/geometry and subsequent actual interaction/no-write checks are retained. The fixture now protects all displayed groups and the observed minimum pair coverage (desktop13,phone4) explicitly, rather than allowing spacing to silently suppress useful routes. These are fixture baselines, not universal percentage promises. + +## Explicit organization must publish navigation state after one-shot layout + +Review of6ba55f2 found a minimap lifecycle defect. The exact reset/startup/tick/end callbacks with real d3-force moved the pinned nodes but left the old minimap coordinates after its five800ms startup polls had finished. Organization restarts at alpha0 and emits only one tick, so it cannot rely on an every-eighth-tick publish. Position-only save updates do not reinitialize the renderer or restart the finite polling effect. + +A native desktop regression waits until the initial minimap is populated and the finite startup window has ended, then wraps the existing receiver, clicks the actual Organize action and compares every independently read organized card coordinate with the received minimap positions. It fails against6ba; the failed headed trace is retained. The correction emits a scoped organization event and the existing minimap publisher handles it immediately, including type data, without invoking camera refit or restoring continuous idle ticks. Closed-map reopening keeps its existing snapshot path. + +On2026-10-29 the corrected actual desktop journey passes1/1 in19.3s with one headed worker/zero retries, preserving painted/readable/all-nine-card, original-edge, API save/reload and camera checks as well as the new minimap coordinate receipt. The phone case remains immediate-action coverage while desktop exercises delayed action after startup. Exact-source review/final composition/hosted acceptance remain required; earlier6ba gates are superseded by this source correction. diff --git a/.agents/inbox/2026-10-28-drag-mode-must-survive-modifier-release.md b/.agents/inbox/2026-10-28-drag-mode-must-survive-modifier-release.md new file mode 100644 index 00000000..13a7d501 --- /dev/null +++ b/.agents/inbox/2026-10-28-drag-mode-must-survive-modifier-release.md @@ -0,0 +1,50 @@ +# A drag must retain its starting mode and discard temporary state when it ends + +Date: 2026-10-28. Existing inspector/read-only-gesture, durable-layout and +interaction-context lessons were searched before this follow-up to issue225. + +## Evidence and cause + +Independent probes extracted the actual D3 callbacks from dev a531823 and +candidate6f9379a. In both, holding Alt at pointer-down returned before setting +`_dragStart`. Releasing Alt before movement then read an undefined start and +threw. After a previous real drag, both reused its stale start/neighbor metadata +and persisted an unintended position. Candidate6f9379a also reheated before the +fresh-node error. The underlying missing/stale state predates this PR; the +candidate did not eliminate it. + +The real headed browser negative on6f9379a failed in11.69s with three page errors: +`Cannot read properties of undefined (reading 'x')`. Command: +`playwright test tests/e2e/graph/layout-recovery.spec.ts --grep 'latest real drag' +--project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 +--trace=retain-on-failure --reporter=json`. Only an owned disposable graph was +used. The failure occurred in the fresh-node Alt-release sequence before the +normal drag/recovery steps, so that run proves the fresh-node failure only. + +## Corrective rule + +Choose drag versus connection intent at pointer-down. Clear old temporary state +before making that choice. Only a gesture initialized for movement may move, +pin, reheat or enqueue positions; changing a modifier mid-gesture must not +reinterpret it using missing or stale metadata. Clear temporary start/neighbor +state and the dragging flag on every end in the active graph/simulation scope, +including no-op and connection exits. Preserve the existing scope guards, +accepted-position queue and durable recovery protocol. + +## Regression and limits + +The existing actual latest-drag recovery journey now releases Alt during a +fresh gesture and again after two real drags. It checks no page errors, unchanged +node positions, no extra queue attempts and unchanged durable receipts before +the original reload/IndexedDB hydration/retry checks. Its result is a real JSON +attachment file. The inspector desktop/phone no-write journeys also reran. All three local +journeys passed in53.80s, zero retries, with fresh and post-drag Alt gestures +leaving positions unchanged and zero page errors. This is focused browser +evidence, not a replacement for the full hosted matrix. +Independent callback probes distinguish pre-existing behavior from the current +correction; they do not replace actual native-browser evidence. + +This does not establish every relationship-creation gesture or solve item +obstacle routing. Those retain their own interaction and geometry acceptance. +Final exact-source review, complete relevant CI/artifacts and actual post-dev +acceptance remain required; retain the failing run as well as the correction. diff --git a/.agents/inbox/2026-10-28-edge-deletion-must-refresh-subscribed-counts.md b/.agents/inbox/2026-10-28-edge-deletion-must-refresh-subscribed-counts.md new file mode 100644 index 00000000..b1c6801b --- /dev/null +++ b/.agents/inbox/2026-10-28-edge-deletion-must-refresh-subscribed-counts.md @@ -0,0 +1,62 @@ +# Refresh every subscribed edge query after deleting a relationship + +On 2026-10-28, inspecting the actual `priority-typography10` desktop/phone +JSON exposed a count mismatch after the intentional BLOCKS deletion: canonical +API data and the graph dock had four relationships, while the graph-selector +header still read `4 nodes, 5 connections`. Existing cache/count lessons did +not cover this query-variable mismatch. + +The stronger desktop assertion failed before the fix: + +```text +priority-count11: 0 passed, 1 failed, 20.15 seconds, zero retries +Expected: 4 nodes, 4 connections +Received: 4 nodes, 5 connections +Timeout: 5000ms (14 reads of the unchanged header) +``` + +The failed report, screenshot and trace remain in the owned evidence directory. +The exercised command was the existing `relationship-priority.spec.ts` with +`--grep 1440 --headed --workers=1 --retries=0`, using only owned graph fixtures. + +Cause: the selector observes `GET_EDGES` with a source-graph filter; the graph +observes the same document with source-or-target filtering. The relationship editor had no mutation refetch configuration and its parent +callback refreshed only the renderer’s variables. Apollo stores those root lists +separately, so normalized entity updates alone did not remove the stale ID from +the selector's list. The selector does not poll. + +The correction supplies `GET_EDGES` itself to the mutation's `refetchQueries`, +on the editor’s explicit Delete action, so Apollo refreshes matching observed +queries using their own variables. The +installed `QueryManager.getObservableQueries` implementation was inspected to +verify document matching. The editor awaits those refetches before reporting completion. Its separate +flip workflow is unchanged; no graph membership, priority policy or persisted +position changes. + +Corrective rule: when deleting records represented by independently filtered +query lists, refresh the subscribed lists or explicitly update each affected +cache field. Test the user-facing count as well as the canvas and server state. +Do not add polling merely to hide a mutation invalidation gap. + +The desktop/phone priority journey now requires the header to change from five +to four connections after the one authorized deletion, alongside original-ID +fallback, rendered text/wire geometry, no extra mutations and unchanged nodes. +Final committed-source CI and independent review remain required. This is a +deletion-path correction, not proof of every other mutation or cross-client +refresh path. + +An initial local attempt changed the graph canvas mutation instead of the +editor’s actual Delete action. `priority-count12` still failed the desktop header +assertion; that attempted source change was reverted. Its phone case separately +failed an earlier keyboard-return assertion before deletion; that report and +trace are retained for independent review. A passing subsequent count test alone +does not explain or erase that separate failure. + +With the correction on the actual editor action, `priority-count13` passed both +complete desktop/phone journeys in 25.22 seconds, zero retries. The retained +final typography samples now show `4 nodes, 4 connections`; all original API, +priority, focus and geometry assertions also passed. + +Independent review traced the separate count12 focus failure to stale label +clearance after the relationship panel closes. Its deterministic failing-before +regression and correction are in [the priority context lesson](2026-10-28-priority-controls-must-preserve-context.md). diff --git a/.agents/inbox/2026-10-28-fixture-extras-must-share-cancellation.md b/.agents/inbox/2026-10-28-fixture-extras-must-share-cancellation.md new file mode 100644 index 00000000..1f55afe2 --- /dev/null +++ b/.agents/inbox/2026-10-28-fixture-extras-must-share-cancellation.md @@ -0,0 +1,50 @@ +# Additional fixture edges must share ownership cancellation and draining + +Observed 2026-10-28 UTC during independent review of +`2df89e003327cc5065ca48ea2fe0e89fcd6f4d47`. The existing cold-fixture and ownership +lessons were searched first. This is the same timeout class at an extra-write +boundary, not a reason to raise a browser test timeout. + +## Evidence + +The reviewed nested fixture added four batches after `ownedGraphIds.seed()` had +resolved. Those writes were outside the owner's AbortController/pending-task set. +Review identified that a setup timeout could start cleanup while later batches +continued. This race was found in source review; it was not observed against a +real database and is not claimed as a reproduced production incident. + +```text +2026-10-28, Node 22.18.0, browser-free child runner +node --test --test-name-pattern=late-edge-response tests/lib/runner/owned-graph-fixture.test.mjs + before seed support: 0 passed, 1 failed (1.59s) + old seeder ignored additionalEdges; child succeeded instead of exercising + the required delayed extra-edge timeout + +node --test tests/lib/runner/owned-graph-fixture.test.mjs + corrected final source: 4 passed, 0 failed, no skips (6.07s) + observed order: create graph, create nodes, request two edges, finish response, + unroute, delete edges, delete items, delete graph, verify graph absent + no subsequent edge batch after timeout +``` + +The regression runs the actual owned fixture/seed helper through Playwright's +runner with a fake Page. It launches no browser or database. Retained logs: +`schematic-owned-extra-edges-negative.log` and +`schematic-owned-extra-edges-final.log` under the owned evidence directory. + +## Corrective rule + +Pass additional typed edges as source/target indices into the existing seed +operation. Validate indices before writes, resolve IDs after node creation, and +append the extra edges to the same bounded, signalled batch loop. Verify every +batch's returned count. The owned fixture then aborts future batches and drains +the in-flight response before edge-first cleanup. Do not create a second, untracked +mutation loop after a tracked seeder has resolved. + +## Limits + +The small regression proves lifecycle ordering under a controlled delayed response, +not backend correctness or browser acceptance. The 72-node real CI fixture must +still prove all 71 hub neighbors, exactly one self-loop, exact internal/bundle +partitions and unchanged final API readback. Preserve its existing fixture/test +budgets, one worker, zero retries and strict cleanup. No heavy local stack is needed. diff --git a/.agents/inbox/2026-10-28-freeze-index-for-strict-runs.md b/.agents/inbox/2026-10-28-freeze-index-for-strict-runs.md new file mode 100644 index 00000000..74a2698c --- /dev/null +++ b/.agents/inbox/2026-10-28-freeze-index-for-strict-runs.md @@ -0,0 +1,47 @@ +# Freeze the index as well as runtime source during strict test runs + +Date: 2026-10-28 UTC. Existing mobile touch evidence and the unified runner +already require matching source fingerprints. A layout recovery run exposed a +workflow mistake: staging the prepared implementation while the gate was active +changed the repository fingerprint even though the running renderer's bytes +were unchanged. + +## Evidence + +The owned run used: + +```sh +TEST_URL=http://localhost:28818 QC_BROWSER_PORT=29818 xvfb-run --auto-servernum \ + node tests/run-unified.mjs --sequence layout-recovery \ + --out /tmp/graphdone-resolution-20261027/layout-recovery-strict-final +``` + +During execution, `git add` staged the implementation, tests and documentation. +The 2026-10-28 result was: + +```text +layout-recovery: passed, 9 passed, 0 failed, 0 warned, 0 skipped (247.5s) +source-integrity: failed +The tested repository changed during this run; rerun against a stable source tree. +overall: FAILED, 9 passed, 1 failed, 0 warned, 0 skipped +``` + +The report remains retained. It is not counted as an accepted run. The existing +runner detected the mutation without a new implementation or weaker threshold. + +## Cause and corrective rule + +The source freeze includes repository/index state, not only files imported by +the running browser. Finish edits and staging, commit the candidate, then start +the exact-head gates. During a gate, keep that worktree's source, documentation, +index and runtime fixed. Read-only review and work in other owned worktrees can +continue. Wait for exit and inspect the entire report, including integrity and +execution records, before editing or crediting its case count. + +## Limits + +This incident establishes a provenance failure, not a failed browser behavior. +It also does not authorize ignoring source-integrity when only staging appears +to have changed. A subsequent complete, frozen-source run is required. Normal +ignored test outputs and owned external evidence remain separate from tracked +source; do not change the harness exclusions to hide an implementation mutation. diff --git a/.agents/inbox/2026-10-28-graph-controls-need-shared-flow.md b/.agents/inbox/2026-10-28-graph-controls-need-shared-flow.md new file mode 100644 index 00000000..20752eb2 --- /dev/null +++ b/.agents/inbox/2026-10-28-graph-controls-need-shared-flow.md @@ -0,0 +1,59 @@ +# Graph controls need shared flow and explicit event boundaries + +2026-10-28, PR #223 / issue #222. CI 40731080444 tested merge +`7eb3b6085282c53b5a48ce806bd1e35bf21424c8` from source `b33febb`. +Its durable-layout report contains four actual passes and five actual failures, +plus the execution-failure record. All five failures show the connection Browse +button intercepting pointer clicks intended for layout Retry/Discard controls. +The independently verified smoke artifact SHA-256 is +`c52d20e7` (prefix; complete digest retained in the GitHub artifact metadata). +Screenshots and traces are retained in the failed run. Four downstream browser +stages were skipped; this run is not acceptance. + +Cause: status and connection controls independently occupied bottom-left space +at the same z-index. Increasing text size exposed an existing ownership problem. +A second headed regression at 1440/390 opened the relationship browser's Edit +button, but the parent document outside-click handler immediately cleared the +selected edge, changing the new dialog into Create relationship. + +Corrective rule: put status, trace and connection controls in one responsive, +scrollable flow dock in both item and group views. Include each control's actual +footprint in routing/label obstacles. Do not fix overlapping controls by changing +paint order or forcing test clicks. Keep browser-panel clicks inside its event +boundary so opening an editor cannot also trigger outside dismissal. + +Validation commands use one headed Chrome, one worker and zero retries: +- `playwright test item-wire-consumers.spec.ts layout-recovery.spec.ts --grep + 'item wire paths|reload|unavailable storage'`: three passed in 36.7s. Exact + browser Edit now checks original source, target and type without data writes. +- `playwright test layout-recovery.spec.ts --grep + 'quota-failed|retry after real|pending layout keeps'`: three passed in 42.2s. + The pending-layout journey also checks disjoint status/Connections bounds, + normal Retry/Browse/Discard clicks at 1440 and 390px, and actual API/receipt + state. Existing durability and camera assertions remain unchanged. +- Web `tsc --noEmit` passed using the owned dependency fixture. This is scoped + evidence, not a pinned clean-install or complete CI result. + +Limits: the dock reserves control space, not arbitrary card-to-overlay clearance. +Full exact-source CI, actual artifact review and final dev checks remain required. +The separate priority feature has additional desktop/phone browser coverage; +its success does not establish this PR's full acceptance. + +The first phone trace run rejected the dock container as overlapping its own +trace child. The transparent layout container no longer declares itself as a +second obstacle; status, trace and connection controls each retain their actual +measured obstacle. The oracle and its overlap assertions are unchanged. The +failed `dock-trace-phone1` artifacts are retained. +The corrected phone hierarchy/trace journey passed in 25.3s in +`dock-trace-phone2`, including its original overlap/readability checks and the +8px trace-text negative. No oracle assertion was relaxed. + +Independent review of `fe84973` used the actual dock/status components and the +production D3 zoom filter. A wheel over status prevented native scrolling and +changed camera scale from 1 to 0.8705505633. Marking the shared dock with the +existing `data-lod-controls` boundary leaves the wheel default intact and scale +at 1. The pending-layout browser case now adds a 390×500 viewport, requires +actual dock overflow, wheels over its status area, and verifies native scrolling +with an unchanged camera before discarding the owned receipt. +The complete amended pending-layout journey passed in 15.7s on 2026-10-28, +with one headed Chrome, one worker and zero retries (`dock-native-scroll1`). diff --git a/.agents/inbox/2026-10-28-hidden-labels-need-valid-measurement.md b/.agents/inbox/2026-10-28-hidden-labels-need-valid-measurement.md new file mode 100644 index 00000000..24de23b6 --- /dev/null +++ b/.agents/inbox/2026-10-28-hidden-labels-need-valid-measurement.md @@ -0,0 +1,87 @@ +# Keep conservative label geometry when hidden SVG measurement is empty + +2026-10-28 UTC, independent review of item-wire source +`d9a0a5855612290b7c765999440b0d1d55b36333` for #220 / PR #221. Existing +guidance was searched in the symptom index and the offset-label lesson. This +was a source-review finding, not a reproduced deployed/browser incident. + +The first placement pass can hide an edge-label group before its delayed +150ms sizing callback runs. Accepting an empty `getBBox()` then replaces a +conservative text estimate with padding alone (36 units), allowing a long +label to be admitted using an undersized obstacle/background. Initialize text, +icon and background from one conservative cache, and replace that cache only +when both measured dimensions are positive. Hidden labels must not become +small merely because they are not currently measurable. + +The same review found a line-only diagnostic reading `x1.baseVal` after the +renderer changed to SVG paths. Read actual path endpoints through +`getPointAtLength` and transform them through `getScreenCTM`. Searching for +remaining `SVGLineElement` / `x1.baseVal` consumers found this one diagnostic. + +A separate source audit found that directed perpendicular offsets put reverse +edges on the same lane: reversing both the direction and lane sign cancels the +separation. Use a canonical unordered endpoint direction to define lane sides, +while preserving the original route direction for arrows and editing. + +Dated local commands used supported Node22.18, nice10, one worker, in the +existing clean-installed partial probe (not full-tree acceptance): + +```sh +node node_modules/vitest/vitest.mjs run --root packages/web \ + src/lib/__tests__/edgeRouteGeometry.test.ts --maxWorkers=1 --no-file-parallelism +node node_modules/typescript/bin/tsc --noEmit -p packages/web/tsconfig.json +node node_modules/@playwright/test/cli.js test tests/e2e/graph/item-wire-consumers.spec.ts --list +node --test --test-concurrency=1 tests/lib/runner/unified-cli.test.mjs +``` + +Results: 11 geometry cases passed in0.722s (9ms bodies); scoped web types passed +in3.72s; two browser cases discovered, **not executed locally**; 32 CLI harness +cases passed in4.56s without a browser. Scoped flat-config lint over both +components/helper/test passed with0 errors and314 warnings. An initial lint +invocation from the uninstalled worktree failed to resolve a legacy config; +the corrected command ran from the probe's web package with +`ESLINT_USE_FLAT_CONFIG=true`. That setup error is not a source lint result. + +The new blocking `item-wires` sequence is enrolled in PR/full profiles and CI +after smoke, before longer suites, with one browser worker and zero retries. +It exercises actual SVG paint/hitboxes/arrows, pointer dragging, wire/label +editing and initially hidden label sizing, retains PNG/JSON, rejects writes, +compares original graph data and uses owned fixture cleanup. CI execution and +artifact review remain required. The route is still straight; these changes +do not establish bounded obstacle routing, visible detail budgets, complete +spaghetti detection or closure of #220. + +Independent review of `9931b3c` required stronger browser oracles before +acceptance: pinned physics can be at rest while the450ms Fit transition is +still moving the camera, and paint checks must inspect ancestors and actual +stroke/fill. The correction waits for both `atRest` and `cameraReady`, rejects +hidden ancestors/absent paint, and deliberately hides wire and arrow groups or +removes a wire stroke to prove rejection. It also compares route endpoints to +the original API-directed cards and checks the editor's original direction and +selected type. Relationship type buttons expose their selected state through +`aria-pressed`. Local discovery still finds2 cases and scoped web types pass; +actual browser acceptance is pending. These were source-review findings, +not locally executed browser negatives. + +On2026-10-28, exact source `93fe6d2` CI40646515194 failed the hidden-size case: +expected at least104, received94.90625. The other item-wire case passed, +including paint negatives and actual dragging/editing. Chromium had returned +valid measured text dimensions smaller than the conservative estimate; the +assertion incorrectly assumed hidden SVG always measures zero. This is a test +precondition defect, not evidence that valid94.90625-unit geometry clips text. +The amended case deliberately returns an empty box only for hidden edge text, +counts that branch and retains the full-size/background/visibility assertions. + +The owner then requested a visible headed browser. A single installed Chrome, +one worker, zero retries, on isolated preview28917/API28918 and the previously +owned Neo4j28687 exercised the regression. With only the positive-measurement +production guard removed from the disposable preview, the actual case failed +with width36 versus104, after confirming an empty measurement. Restoring the +unchanged production guard made both item-wire cases pass in21.9s. Traces, +screenshots and logs are retained under `item-hidden-negative` and +`item-wires-headed-positive` in the20261027 evidence directory. This reused a +matching dependency installation for scoped preview evidence; it is not a +clean-install/full-tree acceptance run. The source PR remains subject to fresh +CI and independent review. The owner's further feedback about clipping, +overlap, tiny text and wasteful cards requires broader visual acceptance; +passing these two interaction cases does not resolve that feedback. diff --git a/.agents/inbox/2026-10-28-inspector-controls-must-fit-system-fonts.md b/.agents/inbox/2026-10-28-inspector-controls-must-fit-system-fonts.md new file mode 100644 index 00000000..a5ea104a --- /dev/null +++ b/.agents/inbox/2026-10-28-inspector-controls-must-fit-system-fonts.md @@ -0,0 +1,63 @@ +# Navigation must fit without shrinking its labels + +Date: 2026-10-28. Existing readable-preview, shared-symbol and browser-evidence +lessons were searched before this follow-up to issue225. + +## Evidence and cause + +The actual3ec563c showcase laptop screenshot clipped the inspector's Diagram +tab. That showcase had four items and no connections; it establishes this +control-fit defect, not routing or scale acceptance. The legacy row was not new +to this PR, so the observation alone does not attribute its origin to226. + +A DOM-only probe on the owned headed graph copy set the inspector's font to +DejaVu Sans. Output: clientWidth286, scrollWidth303; Diagram right1363.53 exceeded +the panel's right1348. `fc-match 'DejaVu Sans'` returned +`DejaVuSans.ttf: "DejaVu Sans" "Book"`. The real long-content desktop regression +on3ec563c then failed in19.72s with29px horizontal overflow (one pixel allowed). +Command: `playwright test tests/e2e/graph/inspector-readability.spec.ts --grep1440 +--project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 +--trace=retain-on-failure --reporter=json`. The inspector's vertical scrollbar +also consumes space; a local-font screenshot alone missed the intrinsic label +width constraint. + +Three unbroken labels, icons, gaps and padding had a combined minimum width +larger than the panel. Flex shrinking does not override that content minimum. +Hiding overflow or reducing working text would conceal the symptom. + +## Corrective rule + +Use24px mode icons with at least44px targets, accessible names and pressed state. +Keep readable hover/focus captions in a viewport-bounded portal so the scrolling +panel cannot clip them. Escape dismisses the hint before the parent inspector; +scrolling, wheel gestures, resize, outside pointer-down, mode changes and a new +inspected item discard stale hints. An unavailable Diagram remains keyboard +discoverable through aria-disabled with an explanatory caption; activation must +not change the mode. Never confuse aria-disabled with the native disabled +property when writing an oracle. + +## Regression and limits + +The actual inspector journey applies a wider system font and checks zero panel +overflow, all three44px targets, hover and keyboard-focus captions, viewport +bounds,14px/500 text floors and Escape dismissal in docked, compact and short +windows. It records control/tooltip geometry in JSON and actual tooltip PNGs. +Component cases check unavailable-mode behavior, one hint at a time, context +reset and dismissal. The first component assertion used jest-dom's native-only +toBeDisabled matcher against aria-disabled; the corrected oracle verifies the +ARIA state plus guarded activation and leaves the real wider-font negative intact. +TypeScript also rejected Playwright's `exact` option in Testing Library role +queries (TS2769); use its supported exact string-name matching instead. + +This does not prove every font, localization or every unrelated app control. +Required full CI and exact-source artifact review remain necessary; screenshot +review caught the defect that earlier default-font local assertions missed. + +Final local evidence on 2026-10-28: the two real desktop/phone browser journeys +passed in 47.09s with no retries. Eight PNGs and two JSON files were inspected; +all six docked/short/compact control groups had zero horizontal overflow, +44px targets and 14px/600 captions inside the viewport. The fixtures contained +301 items/564 edges and 72 items/132 edges. Both recorded zero writes and no +page errors. Nine focused component/projection/rendering tests passed in 3.12s; +web TypeScript passed and scoped ESLint reported zero errors (two existing any +warnings). These focused checks are not a substitute for full hosted CI. diff --git a/.agents/inbox/2026-10-28-inspector-previews-need-readable-levels.md b/.agents/inbox/2026-10-28-inspector-previews-need-readable-levels.md new file mode 100644 index 00000000..696bda02 --- /dev/null +++ b/.agents/inbox/2026-10-28-inspector-previews-need-readable-levels.md @@ -0,0 +1,117 @@ +# Compact inspectors need readable content and read-only gestures + +Date: 2026-10-28. Issue225 follows the accepted shared typography work in223. +Existing readable-symbol, preview-style, control-flow and priority-context +lessons were searched first. + +## Evidence and cause + +The active `NodeInspector` still used a markdown renderer with inline code at +0.85em and fenced code at0.7/0.8rem. A focused phone browser case using the +unchanged a531823 `NodeContentRenderer.tsx` failed in30.39s: actual inline code +was11.9px, fenced code12.8px, both weight500. Other preview/caller files were +from the current candidate; this is an exact old-renderer reproduction, not a +claim that the entire old checkout ran the new test. + +Command: `playwright test tests/e2e/graph/inspector-readability.spec.ts +--grep390 --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 +--retries=0 --trace=retain-on-failure --reporter=json`, using owned local fixtures. +The old nested preview also used6px SVG labels, truncated names to18characters, +and could draw600 thin straight lines. Its300-item/600-edge fetch caps were +not distinguished from total graph counts. + +The first corrected desktop/phone run passed the phone but failed desktop +no-write acceptance. Trace requests showed two `UpdateWorkItems` calls writing +the same owned parent position(-300,-300), before opening the child graph. +The D3 drag lifecycle starts on an ordinary node click and unconditionally queued +positions at end, even without movement. The requests were blocked and retained; +unchanged API data alone would have missed this defect. + +After delaying node pinning/reheating until actual drag movement, both journeys +passed in35.57s, with301/564 and72/132 actual child items/edges,14px/500 floors +in normal/compact/preview text and zero writes/errors. This intermediate green +run was visually insufficient: markdown's long cell squeezed another table +column into single letters, and the desktop minimap covered the inspector's +bottom. The final candidate reserves a real table column width with horizontal +scrolling and bounds the docked inspector above a shown minimap. Existing +actual drag/recovery acceptance passed in13.06s. The final focused run passed +both inspector journeys in37.97s; all four screenshots were inspected. Seven +pure/component cases passed in1.90s. The final preview query includes either +endpoint in the child graph so incoming boundary connections are retained as +pending context. Same-named representative items are deduplicated only in the +visible examples; underlying membership/counts are unchanged. + +The component oracle initially counted two decorative Lucide icon lines as +old diagram wires; it now distinguishes aria-hidden icons from diagram content. +The first type check also rejected `replaceAll` under this repository's target; +use the compatible global replacement without changing the toolchain. + +Independent review found another untested boundary at cfd7c24: a long parent +title was wrapped in a non-scrolling header. A DOM-only408-character probe in +the owned graph copy produced a498px inspector with840px header, tabs below its +bottom and a zero-height body. The real phone regression then failed in13.32s: +title height336px exceeded the96px limit. The correction bounds the full title +in a keyboard-scrollable region and lets the whole panel scroll; header/tabs/body +cannot shrink the contents to zero. Desktop/phone regression now includes long +parent titles, keyboard access to the full text and a560px-high window. The +first attempted negative accidentally reused the previous short-title spec due +to a same-source/destination copy; its14.37s green result is not negative proof. +Only the subsequently copied long-title spec and retained failing trace count. +The first corrected run still failed phone short-window visibility (88.76% of the +code block, below the90% oracle). The harness now awaits End/Home scrolling to +finish and explicitly centers code after resize. A second run passed that check +but could not find Expand: resize had legitimately selected compact item detail. +The journey now uses a real wheel gesture to restore full detail before Expand; +it does not mutate the D3 transform or relax the visibility requirement. +Final desktop/phone passed2/2 in39.20s, with both inspector variants,560px window, +zero writes/errors and eight file-backed artifacts. All six PNGs were inspected. +The existing smoke gate passed6/6 in116.87s on cfd7c24; final complete CI must +validate the correction as well. Stored child counts are handed to the existing +portable-authoring issue217 with source evidence; do not silently fix fixture +properties to conceal that remaining writer-contract limitation. + +## Corrective rule + +Compact mode may change spacing and heading hierarchy, but must keep working +text at least14px/500. Render fenced code in one semantic pre/code block with +horizontal scrolling, useful line height and readable comments. Tables must +preserve usable column widths; scroll within the inspector rather than squeezing +words to single characters or widening the entire panel. + +At thumbnail scale, present bounded semantic groups, full representative names +and typed/directed grouped connection counts. Preserve every original in the +underlying partition, including parallel/reverse/self links and missing +endpoints. Distinguish capped data and unavailable/loading relationships from +known totals or zero. An Open graph action reaches the complete graph. +The preview is a structured summary, not a scaled copy of authored positions. + +Opening or inspecting a node is not a drag. Do not pin/reheat on pointer-down +alone or enqueue a position just because a drag-end event fired. Only accepted +changed positions update the locally authoritative snapshot. Keep ordinary +real drag, durable receipts, account/graph ownership and recovery behavior. + +## Regression and limits + +Pure/component cases cover conservation, stable bounded sampling, unknown types, +long multilingual names, loading/errors/empty states, limits and navigation. +The real desktop/phone journey covers normal and compact code, an injected +8px/400 negative, long-line scrolling, table widths, minimap clearance, a dense +nested preview, partial counts and opening the complete child graph. It records +actual typography JSON/PNGs as files for the unified new-user sequence, guards +mutations before navigation, and compares API snapshots. + +The nested fixture must connect both the denormalized `subgraphId` and actual +`subgraph` relation: setting only the ID produced a misleading zero-count parent +sheet in the intermediate screenshot. The fixture uses the supported GraphQL connect input. A follow-up fixture run +failed because the new required target variable was omitted; correcting that +variable yielded two passing journeys in37.97s. The parent card still shows the +server's stored zero counts: Graph.nodeCount/edgeCount are defaulted properties, +not live aggregates. Connecting the graph alone does not repair these counts. +The inspector preview derives counts from its actual fetched data. Do not claim +this fixture correction fixes stored count maintenance or portable import/MCP +semantics (issue217). + +Main item obstacle routing, route/minimap clearance and larger rendering/loading +budgets remain220/193. Full relevant CI, exact-source independent review, actual +artifact inspection and post-dev acceptance remain required. Keep the failed +and visually rejected runs; a green assertion set alone is not visual acceptance. diff --git a/.agents/inbox/2026-10-28-lod-return-needs-hysteresis-and-focus.md b/.agents/inbox/2026-10-28-lod-return-needs-hysteresis-and-focus.md new file mode 100644 index 00000000..deb2b173 --- /dev/null +++ b/.agents/inbox/2026-10-28-lod-return-needs-hysteresis-and-focus.md @@ -0,0 +1,70 @@ +# Restoring a LOD camera also requires its hysteresis and focus state + +Observed 2026-10-28 UTC during independent review of nested LOD candidate +`43ada01ffeb213006289db5e20d3e36cebf2464c` for #215 / PR #216. +Existing renderer-ownership and title-refresh lessons were searched first; this +incident concerns history-dependent display cuts and focus after replacement. + +## Dated evidence + +The candidate passed 18 model cases, two strict real-browser journeys and +independent randomized conservation checks, but those did not exercise Auto +inspection return inside a hysteresis band or focused-parent replacement. + +```text +2026-10-28 +npm run test -w @graphdone/web -- src/components/SemanticGraphOverview.test.tsx +before correction: 4 failed, 1 passed +entry camera scale .4, original [System] +inspect at 1.2, Back at .4 -> [System/A, System/B] +focused System at .4, Auto zoom .46 -> document.activeElement BODY +live removal of focused summary -> BODY +entry overview at1.1, inspect1.2, Back1.1 -> summaries remain hidden +``` + +The actual desktop browser negative against the same application source restored +the exact entry camera but failed `2 groups · 72 items` after Auto Back: the cut +had expanded. Existing 5s assertion and 10s action limits remained unchanged. +Evidence is retained in `/tmp/graphdone-resolution-20261027/` as +`nested-review-all-unit-negative.log`, `nested-review-browser-negative.log` and +`nested-review-browser-negative-artifacts/`. Independent actual-component probe: +`/tmp/graphdone-review-nested-32eoo5dq/negative.log`. + +After correction the focused component/model run passed 23/23; types passed. +The final frozen candidate and real-browser regression still require their own +strict validation and independent review in PR #216. A local probe is not +published acceptance evidence. + +## Cause and corrective rule + +The saved camera and manual cut omitted Auto's previous depth and overview +visibility. Hysteresis makes the same scale yield different states depending on +the path taken. Save both state machines at inspection entry and restore them +with the camera. Test both the nested-depth and overview-visibility bands. + +Auto expansion removes the focused DOM button. Preserve an owned focus receipt +with canonical member IDs, then reconcile it to a surviving visible descendant +or ancestor (or the complete group list) when that element disappears. Do not +move focus when another control owns it. Test expand, collapse, live removal +and deliberate focus movement independently of mouse navigation. + +## Validation resource correction + +Later full coverage at frozen1baac30 timed out the 40-member component fixture at +its existing5s limit; concurrently the 301/370 desktop browser exceeded its +existing120s interaction limit. Retain both failures. Five component assertions +need only four members under one parent/two children: a clean installed isolated +worktree proved the smaller fixture still fails the same four old-source bugs +and passes all five on corrected source with coverage. This does not reduce the +72-item/301-item browser datasets. Run CPU-heavy local coverage and browser gates +serially after observing interference; do not raise limits or accept a retry as +proof of a fix. Fresh exact-source serial validation remains required. + +## Limits + +This restores view state only. It must not write source positions or turn summary +IDs into persisted nodes. Removed items cannot retain nonexistent representatives; +use an accessible fallback. It does not establish progressive loading, physical +phone performance, per-region spatial budgets or graph capacity. Carry the lesson +and regressions through normal review; do not equate this local note with a +completed integration. diff --git a/.agents/inbox/2026-10-28-offset-label-drag-needs-offset-path.md b/.agents/inbox/2026-10-28-offset-label-drag-needs-offset-path.md new file mode 100644 index 00000000..e65076a1 --- /dev/null +++ b/.agents/inbox/2026-10-28-offset-label-drag-needs-offset-path.md @@ -0,0 +1,47 @@ +# Project dragged labels onto their displayed offset path + +2026-10-28 UTC, detailed item-wiring preparation for #220. Existing guidance +was searched through `.agents/issues/INDEX.md`, `edgeLabelLayout.ts` and the +schematic lessons. This is a controlled helper regression found before D3 +integration, not an observed failure in a deployed item router. + +The independent review of `eee4063c43e619a33c48fd0408bb9dc2dd45cc92` asked for +an offset-label drag case beside a close return wire. The helper initially +projected the pointer onto the wire centerline. On a U-shaped route whose +horizontal legs are 40 units apart, the lower leg's upright label is offset22 +units upward. Its center is18 units from the upper wire and22 from its own. +Nearest-wire projection therefore selects the wrong leg even with the prior +fraction available as a tie-breaker: the distances are not tied. + +Command, supported Node22.18, existing clean-installed partial probe, one worker: + +```sh +node node_modules/vitest/vitest.mjs run --root packages/web \ + src/lib/__tests__/edgeRouteGeometry.test.ts --maxWorkers=1 --no-file-parallelism +``` + +Before correction: 9 passed, 1 failed, exit1, 0.713s. The executed new assertion +expected `340 / 440 = 0.772727...` but received `100 / 440 = 0.227272...`. +It passed a requested offset22 while the earlier helper ignored that extra +argument. The production API was then extended explicitly, not the assertion +relaxed. After correction: 10 passed, no skips, exit0, 0.691s (8ms test bodies). +Scoped strict ES2020 TypeScript checking of helper and test also passed. Source +and test were copied unchanged into the probe; this is not full-tree acceptance. + +Corrective rule: use the same upright perpendicular displacement for pointer +projection and label painting. At D3 drag start, retain where inside the label +the pointer grabbed it, then project the adjusted label center onto the offset +route. Preserve icon rotation around the icon's own center. Keep the direct +wire projection available for wire hit testing; it has different geometry. + +The review also found repeated route scans and candidate sorting in placement. +Interpolate each candidate directly on its current segment and retain the best +candidate in one pass. Placement now has O(segments × obstacles + segments) +work with at most four candidates per segment. Runtime callers still need +explicit bounds and a suitable update cadence. A null sampled placement means +no tested slot was clear, not that every mathematical position is obstructed. + +The regression lives in `edgeRouteGeometry.test.ts`. No browser/database/image +suite ran locally, no canonical positions changed, and no item-renderer +acceptance is claimed. Carry this finding with the D3 consumer fix and its +actual pointer/browser gates through normal review. diff --git a/.agents/inbox/2026-10-28-overview-wires-need-interaction-geometry.md b/.agents/inbox/2026-10-28-overview-wires-need-interaction-geometry.md new file mode 100644 index 00000000..506d619c --- /dev/null +++ b/.agents/inbox/2026-10-28-overview-wires-need-interaction-geometry.md @@ -0,0 +1,38 @@ +# Overview wires need interaction geometry and original-edge inspection + +On 2026-10-28 the owner reported that overview links could not be hovered and did not identify their endpoints. At dev `00f0211`, SemanticGraphOverview rendered bus paths under a pointer-events:none overlay and an aria-hidden SVG. A native SVG title was the only description; there was no pointer or keyboard action. + +## Evidence + +- In the owned headed 301-item / 370-relationship copy, the new bus target was reached by a real mouse move. It emphasized exactly two endpoint cards, exposed the directed count, and opened three original rows for the selected route. Back returned focus to the same SVG button. No original user graph was edited. +- Painted-state verification waited for two animation frames: DOM outline/stroke attributes alone did not establish that a just-updated headed screenshot had painted the emphasis. +- `npm exec --workspace=@graphdone/web -- vitest run src/lib/overviewBusHint.test.ts src/lib/semanticRelationships.test.ts src/components/SemanticGraphOverview.test.tsx`: 19 cases passed in 2.33s, including pointer/keyboard inspection, removal of the focused route or entire overview, parallel/future relationship types, and caption containment. +- The existing 301-item headed and 2,000-item / 5,000-relationship headless journeys now exercise a real pointer six pixels away from the visible line, keyboard Enter/Space, Escape dismissal, endpoint emphasis, unchanged camera, and exact directed original IDs against independently seeded group membership. Existing graph snapshot/write guards remain in place. Final-source browser evidence and hosted scale acceptance must pass before integration. + +## Cause and corrective rule + +A visible path is not automatically an interactive or accessible connection. Render a generous transparent stroke target separately from the visible wire, expose keyboard semantics, and use the same route identity for its label, endpoint emphasis and drill-down. Keep canonical selection and authored positions separate from transient inspection state. Preserve original edge IDs across multi-type bundles; do not silently turn a represented route into a different pair/direction query. + +A hover caption is temporary but should seek free space before covering useful content. The bounded caption placement considers at most 16×16 candidates, checks all visible card/control obstacles, and clamps to the viewport. Keyboard focus returns to the original wire or a surviving group when a cut removes it. Escape dismisses the caption, and pointer transfer into it keeps it readable. + +## Limits + +This is the interaction slice of #220, tracked by #231. It does not solve tight overview spacing, iterative connection-aware placement, detailed wire crossings or the router's arbitrary-position grid budget. A phone with no available rectangle can still require a transient caption to cover content; keyboard/tap inspection opens the complete relationship browser. Route-count limits and honest deferred totals are unchanged. Private fixture screenshots are not published as public repository evidence. + +The first updated Core browser run passed both 301-item cases in 118.22s. The final correction also checks keyboard focus when zoom removes the complete overview layer; its local/hosted acceptance is required before closeout. + +Independent PR232 review reproduced a mixed-input failure at `3e2f7e1`: hover A, then focus B without moving the pointer left A emphasized. The root cause was hover taking unconditional precedence and no persistent painted focus cue. Keyboard focus now clears stale hover; a later intentional hover may inspect another route while the focused wire retains its visible cue. The added component control and real desktop/headless fixture exercise that exact sequence. Review evidence: PR232 discussion `4407557233`; the correction must be independently rechecked before acceptance. + +## Review controls and recovery, 2026-10-28 + +The same independent review reproduced unrelated-bus selection at the visible center line: the actual eight-card router produced fourteen routes, but a later 24px hit stroke captured `(412,172)` for source 3 while source 0 was visibly drawn there. `overviewBusHit.test.ts` retains that actual-router negative. Pointer ownership now projects onto cached route geometry and selects the nearest route; equal-distance shared trunks retain the current branch. Click/focus return use that resolved route, while keyboard activation uses its explicitly focused route. + +A real Chrome regression against the actual component then failed at movement from `(200,100)` to `(200,106)`: the first caption had covered the pointer and intercepted further input. Caption placement now prioritizes clearing a 24px square around its anchor before minimizing card overlap and distance. The corrected real-browser control passed 1/1 in 2.00s, with native painter order deliberately pointing to B but center-line hover/click selecting A; Tab and Enter select B with a persistent painted focus cue. The failed run and trace are retained separately. Focused units passed 23/23 in 3.41s; full Core desktop/phone and hosted scale evidence are still required. + +One intermediate Core browser attempt failed before fixture creation because the owned preview API listener was absent. `curl /health` refused the connection and the assigned port had no listener, while the owned database and web server were still running. The prior launcher session was unavailable; the disappearance cause is unknown. The owned development API was restarted detached, on the same loopback-only port and disposable database, with private credentials retained outside the repository. Its health endpoint then reported GraphQL/database healthy; MCP remained offline and was not covered by this preview. Verify service health before classifying an authentication-page failure as a UI regression. Renew only the owned preview login after replacing its session secret. This incident does not establish an OOM, host failure, production outage, or packaged-runtime result. + +The corrected Core desktop/phone journey passed 2/2 in 93.90s with one headed worker and zero retries. The desktop case also transferred focus to a different bus under a stationary pointer, then verified later hover retained the keyboard cue. File-backed screenshots show endpoint emphasis and readable captions; exact original-edge receipts, camera invariance and fixture cleanup passed. Hosted final-source acceptance and independent re-review remain required. + +A second independent Chrome control at8f6e6af found that a shared trunk changed branches between pointer-down and click: hover0→7, move to a shared source0 trunk, down focused0→7, but click inspected0→2 after focus cleared hover. The tie preference now carries focused ownership after hover is cleared. Review discussion4407786254 includes actual-router frozen/corrected evidence. A separate native Chrome shared-branch regression is enrolled in the strict schematic sequence; the two input cases passed2/2 in2.92s. Focused units passed24/24 in3.14s. An intermediate jsdom unit failure came from missing PointerEvent coordinates in its first test; that fixture now supplies MouseEvent-backed pointer coordinates, matching the other controls. Earlier evidence remains retained and is not final acceptance. + +Final-source CI40800378169 at58f0969 failed the large-symbols whole-journey180s deadline. Independent trace audit measured the new bus inspection helper at45.725s(82calls). The deadline fired during the held-pan screenshot; teardown then deleted the owned edges while a later pagination assertion was still in flight. That missing-row assertion is secondary, and final snapshot/write/error guards were not reached. Only four strict reports ran; this is failed, incomplete acceptance. Recorded browser zoom durations3.5235/3.9951/3.0591s stayed under the existing5s bound. The total expanded-journey deadline is now240s(180+45.725+bounded headroom);45sload,5szoom,10sactions, exact identities, final no-write/snapshot/error guards, ownership cleanup and zero retries remain unchanged. Fresh final-source full CI and artifact audit are required; no retry of the failed source is acceptance. diff --git a/.agents/inbox/2026-10-28-persisted-layout-receipts-do-not-authorize-replay.md b/.agents/inbox/2026-10-28-persisted-layout-receipts-do-not-authorize-replay.md new file mode 100644 index 00000000..ba1df45d --- /dev/null +++ b/.agents/inbox/2026-10-28-persisted-layout-receipts-do-not-authorize-replay.md @@ -0,0 +1,117 @@ +# A persisted layout receipt does not by itself authorize replay + +Date: 2026-10-28 UTC. Issue #181. Existing guidance distinguishes the old in-tab +queue from crash recovery; the first persistence prototype exposed additional +ordering boundaries during independent review. The corrections and regression controls now accompany this lesson. Final +exact-head review and CI remain the integration gate; preparatory local results +are not a release or hosted acceptance record. + +## Evidence + +The prototype is based on `525ca4e`, with its tracked patch and relevant source +hashes retained as `layout-reviewed-prototype.patch` and +`layout-reviewed-prototype-sha256.txt` in the owned dated evidence directory. +Twenty-three focused tests passed, but they did not cover the following cases. + +On 2026-10-28, Node 22.18 ran the retained owned probes: + +```sh +node /tmp/graphdone-resolution-20261027/layout-review-probes/probe.mjs +node /tmp/graphdone-resolution-20261027/layout-review-probes/discard-race.mjs +``` + +Sanitized output in `layout-review-negative.log`: + +- Another tab saved `x=20` and cleared its receipt; the old tab then replayed + `x=10` and reported no pending work. +- A server accepted `x=30`, but local receipt cleanup failed. The prototype + reported `acknowledged=false`; retry sent the position again, replacing a + newer server value of `99` in the probe. +- A quota failure left one memory-only position. Discard tried to persist that + unwanted position first and failed, leaving it pending. +- Holding discard's commit and calling flush sent the position being discarded. + +Source review also found that asynchronous recovery could arrive after D3 +initialization without changing live coordinates, and that mapping pending +positions into a fresh work-item array on every camera render invalidated LOD +projection/label memoization. Both require actual browser regressions. + +A second review reproduced a subtler variant after the first seven ordering +regressions passed. A quota-failed, memory-only edit could persist itself on +reconnect after another tab had already saved and cleared a newer receipt: + +```text +oldAfterQuota {"pending":1,"local":0} +beforeOldReconnect {"server":20,"receipts":[]} +afterOldReconnect {"server":10,"sent":[["new",20],["old",10]],"pending":0} +``` + +The matching bundled probe is retained at +`layout-review-probes/corrected-prototype/volatile-stale.mjs`; the new failing +unit assertion is retained in `layout-volatile-negative-unit.log`. A real +IndexedDB/two-tab browser regression now covers the same ordering boundary. + +The held-IndexedDB graph-switch regression also failed before the simulation +scope correction: after A→B, a synthetic pagehide raised **Layout needs +attention** because A's still-live simulation tried to queue through B. Graph +filters prevented a cross-graph server write, but did not establish correct +renderer ownership. `layout-switch-negative.log` retains that failed run. + +The pending-layout LOD browser regression passed with stable memo inputs. +Deliberately recreating the source array during every camera render made it +fail after pan: labels moved between slots, including a 96px row change. The +corrected renderer was then restored byte-for-byte; the negative control remains +in `layout-lod-identity-negative.log`. + +## Cause and corrective rule + +Revision comparison during deletion protects newer receipts from old cleanup; +it does not establish eligibility to send an old receipt. Coordinate current +receipt eligibility, sending and acknowledgement across tabs. Preserve known +server acceptance separately from local cleanup, so cleanup retry cannot +silently become another network write. Serialize discard and flush, and do not +require a failed write to succeed before abandoning memory-only changes. + +Persistence must also preserve eligibility. Compare the staged batch's original +observed graph generation inside the same account transaction that persists it. +Do not silently rebase a failed staged edit after reading a newer generation. +Retain generation fences after ACK/discard; a bounded graph-fence map falls back +to an eviction watermark advanced to the account counter only when an old entry +is evicted. Ordinary unrelated graph saves must not advance that fallback. Validate +safe integers and fail at corruption/overflow. The first fallback used the +current counter and unnecessarily rejected an unrelated graph’s first save; +`layout-fence-unrelated-negative.log` retains that failure. This deliberately rejects some +concurrent independent edits rather than guessing how to merge coordinates. + +Hydrate layout recovery before initializing the graph, or explicitly apply an +eligible recovered revision to the live renderer. Keep semantic source identity +stable during camera changes. Durability status must reflect the actual boundary +completed, including the absence of a successful initial storage read. Stop the +previous owner/graph simulation during layout cleanup and validate its scope at +each persistence boundary; hiding its SVG does not stop its callbacks. + +## Limits and remaining work + +The probes use an owned account-store double; they are evidence of the ordering +failures. The subsequent strict browser run passes eight cases against the owned +API/Neo4j stack, with real IndexedDB and an isolated browser process group killed +by SIGKILL. It includes delayed hydration, reload before ACK, restart, quota and +two-tab ordering, account isolation, deleted/revoked access and Settings discard, +accepted-save cleanup, and unsupported capabilities. See the +[layout recovery record](../../docs/quality/LAYOUT_RECOVERY_2026-10-28.md) for +commands, the pending-layout LOD gate, retained fixture failures and limitations. + +The first process-loss run failed strict profile removal with ENOTEMPTY. Killing +only the browser parent did not bound all profile writers. The fixture now owns +an isolated process group, kills that exact group and awaits the child exit +before reopening/removing its profile. No broad process matching or removal +retry was substituted for ownership. The next run caught a fixture viewport +mistake: the restarted browser used its default width and the desktop selector +was correctly hidden. The fixture now explicitly restores its intended viewport. +Both failures remain retained; neither was an application durability pass. + +Issue #181 excludes server-side multi-actor conflict resolution. A crash after +server acceptance but before durable receipt deletion leaves an ambiguous +at-least-once boundary without a server idempotency/revision contract. Do not +claim exactly-once replay or protection from later writes on another device. +Known acceptance in the live process still requires truthful cleanup-only retry. diff --git a/.agents/inbox/2026-10-28-preview-styles-need-rendered-verification.md b/.agents/inbox/2026-10-28-preview-styles-need-rendered-verification.md new file mode 100644 index 00000000..1e9c641c --- /dev/null +++ b/.agents/inbox/2026-10-28-preview-styles-need-rendered-verification.md @@ -0,0 +1,68 @@ +# Verify emitted typography after refreshing a long-running preview + +On 2026-10-28, the owned GraphDone headed preview still displayed some small +text after the readability change. Searching the symptom index and existing +readable-symbol, dependency-parser and preview lessons found no matching +configuration-refresh incident. + +The preview and reviewed source had identical `packages/web/tailwind.config.js` +bytes (SHA256 `ab33c1897cd34ea18ff04cd6ef644558b59254dcaeb6120225eef4951e8f3ff8`). +Nevertheless, reading the actual browser CSS rules before restart returned: + +```text +.text-xs { font-size: 0.75rem; line-height: 1rem; } +.font-normal { font-weight: 400; } +``` + +The document root was 16px. Visible graph counts, connection status and sidebar +labels consequently measured 12px. The long-running Vite process had received +source updates; source-file identity did not establish emitted-style identity. +Only the owned Vite process was stopped and restarted from its web package with +the same loopback API proxy. Reloading the same headed browser then returned: + +```text +.text-xs { font-size: 0.875rem; line-height: 1.25rem; } +.font-medium { font-weight: 600; } +.font-normal { font-weight: 500; } +``` + +No product source changed. This establishes stale preview styling, consistent +with retained build/configuration state; it does not isolate a specific Vite or +Tailwind cache implementation. A fresh independent exact-source PostCSS compile +also emitted the intended rules in 1.37 seconds, using locked Tailwind 3.4.19, +PostCSS 8.5.28 and autoprefixer 10.4.21. See [the issue evidence](https://github.com/GraphDone/GraphDone-Core/issues/222#issuecomment-6477709156) +and [independent review](https://github.com/GraphDone/GraphDone-Core/pull/223#pullrequestreview-5688035698). + +Corrective rule: after copying build or typography configuration into an existing +preview, restart that owned preview and read computed styles from real controls. +Do not stop unrelated services, assume hot reload refreshed configuration, or +equate matching source hashes with matching generated CSS. Keep one headed +browser and bounded serial checks on the shared workstation. + +The existing desktop/phone relationship-priority journeys now measure the actual +graph title, graph counts, visible Connections dock and Links control. They require the 14px/500 secondary +baseline, 14px/600 heading and 16px/600 control, reject a deliberate 12px/400 +visible counter, restore its original style, and retain named before/negative/after +computed samples in `priority-typography.json` using file-backed attachments. + +Limits: these samples do not audit every page or every font. Earlier local SVG +geometry checks remain evidence for their explicit assertions, not for all app +utilities. The preceding CI build passed but did not retain generated CSS bytes; +neither source compilation nor screenshots are retroactive byte-level inspection +of that CSS. The strengthened journey must pass on its final committed source. + +The first refreshed-style run, `priority-typography10`, passed both complete +journeys in 25.28 seconds without retries. Actual JSON samples showed 14/600, +14/500 and 16/600 before/after, and the injected counter was 12/400. Inspection +also exposed a stale relationship count; its separate cause and correction are +recorded in [the deletion lesson](2026-10-28-edge-deletion-must-refresh-subscribed-counts.md). + +The phone intentionally hides the header count with its existing responsive +CSS. That sample verifies emitted styling and subscribed data, not visible +phone-counter readability. The final test also requires the actual Connections +dock to be visible and readable, records visibility for every named sample, and +applies the 12px/400 negative to that visible dock on phones. + +Final focused run `priority-typography16` passed both full journeys in 25.81 +seconds with zero retries. Its JSON explicitly distinguishes the hidden phone +header from the visible 14px/500 dock and records the visible negative target. diff --git a/.agents/inbox/2026-10-28-priority-controls-must-preserve-context.md b/.agents/inbox/2026-10-28-priority-controls-must-preserve-context.md new file mode 100644 index 00000000..11406411 --- /dev/null +++ b/.agents/inbox/2026-10-28-priority-controls-must-preserve-context.md @@ -0,0 +1,60 @@ +# Priority controls must preserve current focus and inspection + +2026-10-28, PR #224 / issue #220. Independent review of `5aaf6ec` +reproduced two interaction failures with the actual DOM/handlers: +- Open a “Blocks +2” pair, change Links to All and close the pair browser. The + connected SVG trigger loses its tabindex, so calling focus does nothing and + keyboard focus remains on BODY. +- Click the new Links control on a graph with fewer than40 items. The document + outside-click handler clears the selected item and editing edge; the same + control inside semantic overview is protected by that existing boundary. + +Review evidence: PR comments4404185217 and4404202776, summary5687337487. +The initial helper/component suite passed41 cases and routing passed10; those +passes did not cover these transitions. + +Corrective rule: after attempting return focus, verify the actual activeElement +and fall back to the visible Browse trigger if the original no longer accepts +focus. Preserve unrelated focus. A presentation control owns its click boundary; +changing detail must not also dismiss the current inspector/editor. + +`relationship-priority.spec.ts` now opens a pair, switches to All, keyboard-closes +it and asserts Browse receives focus with an unchanged camera. It also inspects +Gateway, opens the native select by pointer, changes All/Essential by keyboard, +and checks the same inspector/camera remain. Both1440/390 headed journeys passed +in25.0s with one worker and zero retries (`priority-focus5`). Original identity, +API snapshot, explicit winner deletion/fallback and cleanup checks remain. +A preceding local command used the preview as its own copy source, failed the +copy, then executed the old tests; `priority-focus4` is not regression evidence. + +Limits: local probes reuse owned dependencies. Full exact-head quality/browser +CI, actual2,000-item priority artifacts, independent correction review and final +dev integration remain required. No claim of full item routing/bus completion. + +## Recompute label clearance when the browser panel changes + +The `priority-count12` phone journey later failed its initial pair-return focus +assertion before any deletion. Independent trace inspection showed the pair +label was still present by ID but had `display:none`: a delayed label-placement +pass had treated the open relationship panel as an obstacle. Closing the panel +did not invalidate placement, so the now-unobstructed label stayed hidden and +focus correctly fell back to Browse. Review comment4404899816 records the trace +evidence; accepting Browse in this case would hide the layout defect. + +A deterministic regression changes All then Essential while the phone panel is +open, verifies the obstructed label is hidden, then presses Escape. On the old +runtime `priority-obstacle14` failed after 16.23 seconds: the label remained hidden +for all 14 reads over the 5-second assertion deadline. Camera/data assertions and +expected focus remain unchanged. + +The label-placement layout effect now also depends on `itemRelationshipScope`. +Mounting/unmounting/changing the panel recalculates its actual obstacle footprint +before the existing next-frame focus restoration. This is bounded placement on +a UI transition, not a new per-frame router. The All-level case still falls back +to Browse when the original label intentionally loses its interactive role. + +The corrected `priority-obstacle15` run passed both full desktop/phone journeys +in 26.03 seconds with zero retries, including the deterministic hidden-to-visible +transition, original-label keyboard return, accurate post-delete counts and +computed typography negatives. The final priority CI still must validate and +retain these assertions on the published source. diff --git a/.agents/inbox/2026-10-28-production-fixtures-must-follow-auth-policy.md b/.agents/inbox/2026-10-28-production-fixtures-must-follow-auth-policy.md new file mode 100644 index 00000000..1f0290db --- /dev/null +++ b/.agents/inbox/2026-10-28-production-fixtures-must-follow-auth-policy.md @@ -0,0 +1,74 @@ +# Production acceptance fixtures must follow the current authentication policy + +Observed2026-10-28 UTC while refreshing #191 from reviewed development work. The +existing CAPTCHA and production-login lessons were searched first. The older +production acceptance helpers posted password-only login mutations, while #183 +now requires a server-issued, action-bound proof for production login. + +## Evidence + +```text +2026-10-28 +node --test tests/lib/runner/production-stack.test.mjs tests/lib/production/sqlite-recreation.test.mjs +before correction:15 passed,2 failed +production fixture login solves a server-issued proof before seeding:failed +production recreation obtains fresh login proofs before and after restart:failed + +node --test tests/lib/runner/production-stack.test.mjs tests/lib/production/*.test.mjs +after correction:19 passed,0 failed,0 skipped +``` + +Retained logs: `/tmp/graphdone-resolution-20261027/main-refresh-captcha-negative.log` +and `main-refresh-captcha-positive.log`. These are executable helper contracts, +not actual HTTPS-stack acceptance. The refreshed composition still requires the +real browser, generated-account, saved-graph and container-recreation gates. + +## Cause and corrective rule + +A previously valid fixture can become invalid after an intentional security +boundary changes. Keep the application policy intact. Request the owned API's +login challenge, validate its shape and advertised work bound, solve that exact +challenge, and submit its proof with the generated credentials. Acquire a fresh +proof for each authentication attempt, including after process recreation; +process-local challenge ledgers intentionally do not survive restarts. + +The proof helper caps work at100,000, rejects unsupported algorithms, invalid +responses and unsolvable challenges, and never logs credentials or proofs. The +actual request transport remains restricted to the owned loopback HTTPS target. +Production browser sign-in must exercise the visible verification widget, not +manufacture or bypass its state. Preserve the fail-closed release workflows and +both production-stack/runtime-image requirements when composing current dev. + +## Additional composition corrections + +Independent review of8746e7d found three other stale fixture assumptions. The +actual owned pruned API image `sha256:4deb93462ed64371cc64a425f099449ad7561f3e4df2a46f336658b44caa8a00` +failed the old persistence probe's npm Cmd assertion before creating resources. +The updated probe checks the declared direct-Node command/server working directory +and runs Node directly; npm is intentionally absent. Actual fresh-image recovery +still requires validation. `main-refresh-sqlite-image-negative.log` retains this +failure without credentials. + +The aggregate workflow correctly required runtime-images, but inherited test +inputs omitted it: the executed workflow suite reported27pass/2fail. Include the +runtime job in successful fixtures and failure/missing-state coverage; do not +remove the actual required gate. `main-refresh-workflow-negative.log` is retained. + +The old production CLI's disposable Git fixture also invoked auto-maintenance. +Independent trace evidence confirmed the child invocation, not a new ENOTEMPTY +failure. Apply the [canonical fixture-local maintenance safeguards](2026-10-27-disposable-git-fixtures-must-own-maintenance.md) +before committing, and verify the actual commit trace contains no maintenance +child. Keep strict removal and do not change the user's Git configuration. + +The grouped correction passed49/49 executable runner, production-helper and +workflow contracts on2026-10-28, with no skips. Retained output: +`main-refresh-grouped-contracts.log`. Actual container/browser acceptance remains +required; this result does not substitute for either. + +## Limits + +Synthetic HTTP contracts are not evidence that nginx, the live server ledger or +the browser widget work together. Retain actual production failures and test the +exact reviewed composition before updating acceptance claims. This correction +changes test infrastructure, not CAPTCHA policy, main authority or deployment +permission. No release or deployment is implied by a successful fixture login. diff --git a/.agents/inbox/2026-10-28-readable-symbols-need-reserved-geometry.md b/.agents/inbox/2026-10-28-readable-symbols-need-reserved-geometry.md new file mode 100644 index 00000000..097f63c4 --- /dev/null +++ b/.agents/inbox/2026-10-28-readable-symbols-need-reserved-geometry.md @@ -0,0 +1,135 @@ +# Readable graph symbols need one reserved geometry model + +2026-10-28: the owner reported clipped relationship labels, overlaps, tiny task/feature text and wasted card space during a headed review. Issue #222 tracks the correction; #220 still owns full item routing and buses. + +Evidence from an owned two-item, two-opposite-relationship fixture: +- Before correction, computed opacity was 0.707107 on both the label group and text: the effective text opacity was 0.5. +- The old card renderer used fixed type dimensions and duplicated character-count wrapping. Enlarging title glyphs at lower zoom did not reserve matching width/height. +- New focused geometry tests initially failed on exact equality of a rotated arrow coordinate (`-1.3322676295501878e-15` versus `0`). Using approximate equality for that coordinate preserves the actual geometric assertion. +- `vitest run src/lib/__tests__/graphSymbolLayout.test.ts src/lib/__tests__/edgeRouteGeometry.test.ts --maxWorkers=1`: 18 passed, 2026-10-28. Scoped web TypeScript passed after correcting D3 nullable overloads and removed transform arguments. These checks reused an owned dependency fixture, not a clean full-tree install. +- Headed inspection at approximately 0.72 zoom showed a compact card footprint 136.41 by 32.98 screen pixels with title ink inside it. This is small-fixture evidence only. + +Cause: text layout, title scaling, card dimensions, route endpoints and label admission were separate decisions. Labels also ignored other wires and arrowheads, and group/child opacity compounded. + +Corrective rule: measure text and reserve its displayed footprint before drawing. Cards and routes consume the same current presentation geometry; secondary rows retreat before readable primary text. Admit a complete label only when its footprint clears cards, labels, wires and arrows; expose original relationships when the drawing cannot display all labels. A small attractive fixture cannot establish large-graph acceptance: test thousands of real loaded items through zoom-out and return-to-detail, with explicit resource budgets and no viewing-induced writes. + +Limits: current item paths are still straight and can cross. This checkpoint does not establish full label/UI obstacle handling, all phone/zoom interactions, bounded thousands-of-item performance, or complete schematic routing. Strict browser regressions, deliberate negative controls, CI and independent review remain required in #222. Preserve earlier failed logs and never count skipped or cancelled tests as passes. + +Follow-up evidence, 2026-10-28: +- The first rendered regression sampled before the final zoom presentation transform was applied and reported undersized labels. The oracle now waits for the actual committed presentation scale and the existing opacity transition. It does not wait for arbitrary geometry violations to disappear. +- The second run classified a zero-area empty description element as a painted collision. The oracle now requires a positive painted footprint; real clipping and overlap remain failures. +- Both desktop1440 and phone390 cases then passed in22.0s with one headed Chrome, zero retries and strict fixture cleanup. Four zoom stages, keyboard compact-card editing, exact relationship access and unchanged API snapshots passed. Deliberately moving a label onto a card and shrinking a card to20px each triggered the intended oracle; restoring the DOM passed again. Retained local run directories are `item-symbols-headed1`, `item-symbols-headed2`, `item-symbols-headed3` under the owned resolution evidence directory. +- Focused geometry:19 passed. Actual unified CLI adapter/strict-evidence tests:33 passed. The added `large-symbols` sequence is headless, single-worker and zero-retry with an explicit2000-item/5000-relationship API fixture, overview clutter checks, identity/snapshot checks, detail readability and timing budgets. Discovery/enrollment is not execution; only actual CI artifacts can establish its result. + +Initial checkpoint351749 CI40704807354 failed the Grow smoke visible-name assertion: `text=.first()` resolved the newly added non-painted SVG `` tooltip. The actual API node/edge delta had passed. The selector now explicitly requires painted `.node-title-text`; node quick-edit uses the same stricter contract. The earlier CI remains a failure; this correction requires subsequent executed smoke evidence. Comprehensive40704807565 passed and does not substitute for that gate. + + +Independent review and executed scale evidence, 2026-10-28: +- PR223 review5684569576 identified an unreachable0.08 test zoom (the real minimap clamps at0.1), unbounded failed label placement attempts, culled labels consuming visible slots/counts, and lost Browse return focus. All four are corrected in the next checkpoint. Pure regression fixtures exercise2000 failed candidates, a total collision-check cutoff and2000 hidden candidates preceding one visible label. The production placement helper has32 labels,128 attempts and200000 obstacle/segment checks as invocation-wide limits; partial checks never admit a label. +- Exact e4 CI40706957761 actually loaded2000 items and5000 edges and passed the eight-group overview clutter/accounting checks. Its first return-to-detail took5847ms against the5000ms gate and failed; the failure screenshot showed zero admitted labels. This remains a failure, not scale acceptance. Corrective work filters placement candidates/obstacles to the visible drawing, prevents hidden labels consuming slots, reuses presentation for unchanged items and rebuilds card content only for uncullled items. The old middle zoom target was not reached in this failed run. +- CI timing JSON now uses retained file attachments, including the timing before a failing budget assertion. The unchanged five-second budget and minimum visible-content assertions must pass in a subsequent actual run; they were not relaxed. +- Complete local headed smoke:6 passed in2.0min (single worker, zero retries). Corrected focus/placement desktop+phone cases:2 passed in22.0s, including Browse Back/Escape focus and compact-editor focus return. Geometry/budget cases:22 passed. Relationship UI cases:14 passed; an initial pagination regression exceeded5s because global accessibility queries scanned1250 buttons, then navigation-scoped queries passed without increasing the timeout. +- Exact relationship access renders at most250 rows per page, with full-result search and explicit original counts. This bounds the new5000-edge browse path; pagination itself does not prove the complete large browser journey. + + +The owner's follow-up rejects tiny type and plain boxes as the visual direction. The revised contract raises full item/overview titles to18px, compact title and edge-label floors to14px, and graph controls to readable14–16px type. Overview cards reserve240x96px; controls reserve their actual two-line height. Subtle layered fills and a restrained teal heading accent distinguish groups. CSS letter spacing on item type text is neutralized so canvas measurement matches displayed header width. + +The6d81752 CI40709921026 large case still failed first zoom at6807ms under the original runner-wall-clock measurement. Its trace shows the timed region included Playwright selector/attribute calls with substantial durations that mix application waiting and protocol/snapshot work; the trace does not isolate those costs (`call867`180338–180836ms, `call869`181294–185485ms, then separate state assertions). The corrected test records both wall time and an in-browser request-to-two-settled-animation-frame duration, with the same5000ms browser budget. This changes the measured boundary and must not be represented as the old wall-time check passing or as a proven performance gain. Both prior failures remain evidence. + +Independent review5684764521 also reproduced stale compact cards exposed during a held pan. Visibility reconciliation now applies current item geometry before exposing each newly visible card, bounded to changed nodes; the large gate includes a held-pan/reveal assertion. Browse focus restoration now preserves an unrelated control focused before the scheduled return.54 relevant geometry/hierarchy/relationship unit cases passed after the typography/layout change; strict final browser and CI acceptance remain required. + +Revised18px/14px typography passed both focused headed desktop/phone cases in23.5s, one worker and zero retries, with strict cleanup and the same deliberate clipping/overlap controls. Scoped web TypeScript passed. This does not replace the pending final large-graph, hierarchy, phone and workflow CI artifacts. + +Independent review5685069307 found the in-browser timer could finish while the parent CSS still hid item layers: child `data-active=false` precedes the passive parent update. The timer now requires the parent gate, actually visible bounded cards and readable titles/labels (or visible overview summaries) for two frames. A deliberate1200ms visibility mask must be included in the measured interval. Hidden overview layout/routing is skipped; projection/accounting remains available and a focused test requires fresh summaries after a hidden live update and return. These changes require subsequent executed validation; neither state attributes nor revised timing alone prove scale acceptance. + +Exact5d CI40713075178 passed the small item consumer/symbol cases but failed the2000-item case on zero visible labels. Its retained timing was3709ms browser/5011ms wall and its overview accounting/clutter passed; this is not full scale acceptance, and the timer at that revision had the review gap above. The route search sampled whole-segment midpoint/end positions, missing the visible middle slice of a long connection. Label candidate ranges now intersect the viewport reduced by the full rotated label footprint, then use the same bounded samples/collision checks; a10000-unit route regression covers this case.24 scoped route/overview cases and web TypeScript passed. Both updated headed desktop/phone cases passed in23.3s, including the deliberate1200ms visibility delay, after correcting the shared timing helper to allow small graphs without an overview layer. The initial negative-control run failed on that missing optional layer; its trace is retained. All local probes still reuse owned dependencies. + +Independent review5685328335 confirmed the content-aware timing fix, but found +clear45-degree labels rejected by their own wire because collision used the +rotated label's axis-aligned bounds. Wire collision now uses label-local axes; +the visual oracle independently clips segments against actual SVG quadrilaterals. +Four diagonal directions have clear/crossing pure regressions. The new headed +negative also exposed that zero-height horizontal paths were excluded from +painted-wire checks; stroked nonzero-length paths now count. Its first restore +attempt selected a different path after Playwright considered the horizontal +negative hidden; restoration now keeps a stable original path locator. Retain +those failing controls rather than treating them as product passes. + +The headed trace review also found12px controls and overlap with the bottom +Connections control. Trace/browser text is now14px, headings16px, trace height +fits content and its bottom spacing clears the control. Overview checks enforce +the14px floor and trace/control separation, including a deliberate8px negative. +The complete72-item phone hierarchy/trace journey passed in43.8s with one headed +browser, one worker, zero retries and strict cleanup;42 scoped geometry/overview/ +relationship tests and web types passed. Exact5b CI40716064148 failed its actual +2000-item visible-content timeout with zero labels; its artifacts are retained. +The current rotated-label correction still needs fresh scale CI and review. + +The corrected diagonal headed journey passed in13.4s: all four zoom stages have +visible readable labels, the injected horizontal crossing is rejected, restoring +the original path clears it, exact two-record browsing and the1200ms visibility +negative pass, and the API snapshot remains unchanged. The existing horizontal +desktop/phone cases also passed in the preceding35s batch; the batch's diagonal +negative failure is retained separately. + +The owner's next visual steering requires stronger wires, icon-based workspace +view navigation with hover/focus labels, and firmer text across the app. The +previous workspace CSS explicitly hid navigation SVGs below1600px and forced +12px captions. Replace that override with44px icon controls,24px glyphs and14px +hover/focus labels, including Escape dismissal and accessible button names. +Body text uses500 weight, control medium600; standard small text and explicit +10/11px utility usages now use a14px floor. Readable wire weight is one3px +screen-space value across zoom/hover restoration, with90% stroke opacity and +non-scaling12px hit targets. Overview buses use3.5px. This changes presentation, +not canonical relationship strength or data. + +Latest serial headed checks: three item-symbol/navigation cases passed36.2s; +desktop1440 and phone360 all-eight-view saved-work journeys passed24.6s; the +phone390 hierarchy/trace journey passed26.0s. Types passed. Small probes reuse +owned dependencies. The priora378 CI40719477629 completed the actual2000/5000 +stage successfully but its broader suite was still running when this visual +checkpoint was prepared. Retain that stage metadata without claiming full-run +or artifact acceptance; new exact-source CI remains required. Independenta378 +review also records ordinary minimap occlusion of a full card's far-right strip; +current geometry checks establish label clearance, not universal card/overlay +clearance during arbitrary panning. + +Independent review5685989836 reproduced a mixed-input tooltip failure: focus +Graph, hover Dashboard, then leave Dashboard; Graph remained focused but lost +its visible name. Track hovered and focused view IDs separately, with explicit +Escape dismissal. The focused label returns when the other hover ends, and +Escape hides it without moving focus. The desktop headed regression passed +14.4s on 2026-10-28 (one worker, zero retries; retained output +`item-symbols-headed15`); final exact-source CI and independent review remain +required. Isolated hover and keyboard checks alone missed this interaction. + +CI40726146104 on tested merge da15eb72 failed the new-user sequence with +39 passing cases, three failing cases and an execution-evidence failure. Both +phone motion traces stalled on a hidden `.node-edit-icon` after the real drag, +API save and camera readiness had already passed; the small-team lifecycle +also attempted that hidden gear. Compact cards deliberately replace header +actions with a title-only representation. The correction targets the original +item ID and uses the supported visible-card double-click, waits for camera +readiness, checks the exact editor title and the updated accessible type label. +The 17-second idle sample, frame count, one-pixel translation limit and all API +assertions remain unchanged. The three failing journeys passed locally in 1.2 minutes +on 2026-10-28 with one headed worker and zero retries. The full-card desktop +SOLO-01 lifecycle also passed in 11.8s. This is a test-consumer +correction, not a runtime change or proof of full final acceptance. + +The same failed CI run's actual large-symbols artifact is clean and unchanged: +2,000 items, 5,000 relationships, 20.403s load; browser zoom 3.033/3.231/2.603s, +wall 4.303/4.503/3.724s; three cards and one admitted label at each detail view, +no attempted writes or page errors. Retain those successful stage artifacts +alongside the failed overall run. The later hierarchy/mobile stages did not run. + +A final active-consumer audit found `RadarChart` bypassing the global typography +with13px axis labels and11px,400-weight,0.7-opacity tick numbers. Dashboard's +expanded distributions and Analytics use this component. The axis measurement +and painted text now both use14px; tick numbers use14px/500/0.9. In the owned +64-item Dashboard preview, all29 painted labels/numbers computed to14px/500 and +none extended outside their SVG bounds at normal chart zoom. This is a targeted +typography check, not a complete responsive chart-layout or manual chart-zoom +acceptance claim. `GraphVisualization`'s legacy12px labels have no active import +consumer beyond the unreferenced `GraphView` file; decorative illustration text +is not application control text. Preserve the broader scope limits above. diff --git a/.agents/inbox/2026-10-28-schematic-gates-must-see-painted-wires.md b/.agents/inbox/2026-10-28-schematic-gates-must-see-painted-wires.md new file mode 100644 index 00000000..6579022c --- /dev/null +++ b/.agents/inbox/2026-10-28-schematic-gates-must-see-painted-wires.md @@ -0,0 +1,72 @@ +# Schematic acceptance must see painted wires, not only clean coordinates + +Observed 2026-10-28 UTC in independent source review of +`41127f064f6f755b357dcbdbcd0761f3143fafea`. The symptom index and prior display +ownership/bus-budget lessons were searched before recording this incident. + +## Evidence and cause + +```text +2026-10-28 source review +git show 41127f0:tests/lib/schematicOverview.ts + required two card elements and at least one path + checked intersections and represented/not-drawn accounting + did not require positive visible card bounds or actual wire paint + +git show 41127f0:tests/e2e/graph/nested-semantic-lod.spec.ts + button name /Request delivery/ matched both the member control + and the new aria-label="Relationships of <same title>" control + +Correction 41b8a03c4c08b750c501e9cbcedf52086e2ed0c4 + independent exact-source review: no remaining findings + no local browser execution; actual extended CI remained pending +``` + +These were source-review findings, not claimed browser reproductions. A hidden +diagram can have zero clutter, and new adjacent controls can make a formerly +unique accessible-name substring ambiguous. Screenshot attachments alone do +not cause automated acceptance to fail. + +## Corrective rule + +Require visible card bounds inside the viewport and painted paths, including +ancestor display/visibility/opacity, stroke paint, width and nonzero path length. +Retain positive route/count requirements and geometric clutter checks. An +intentional opacity-zero wire in the real CI journey must trigger the named +visibility assertion, then be restored in `finally`. Verify actual original +source/target port placement and arrows when incoming buses are introduced. + +Select the member inspection control by its actual visible title, while testing +the adjacent relationship control independently. Keep strict locators; do not +silence the ambiguity with an arbitrary `.first()`. +The existing semantic overview journey's 38-member count needs the same visible +text distinction: relationship buttons include the item title in their accessible +name. Keep the expected 38 source members, and count the actual member controls. + +## Limits + +These checks detect a basic class of visual regression. They do not establish +pleasant spacing, meaningful system grouping, full item/path routing or scale. +Inspect the retained screenshots and exact-source CI reports. Under the owner's +workstation constraint, use small serial local tests and remote browser gates; +discovery-only evidence is never a passing browser journey. + +## Exact control names and exported evidence + +Actual CI 40633460829 at source88d0115 / tested merge025288dafa5fcbefb282fc2c5a313a58dcddebd2 +passed static visual4, smoke6, new-user42 and semantic-LOD2. Both nested cases then +failed at `getByLabel('Relationship type', { exact: true }).selectOption('BLOCKS')`; +the strict sequence correctly failed with its process-exit evidence. Screenshots +showed the control present. The installed Playwright label selector reads the +wrapping label's descendant text, including select options. Explicit accessible +names now keep type, direction and depth labels stable. The exact selectors, +10-second actions and zero retries remain unchanged. Thirteen scoped relationship +component cases passed in 5.48s; corrected browser execution remains required. + +The same artifact exposed an evidence-export gap: the unified Playwright adapter +imports path attachments, while the new tests used body-only PNG/JSON attachments. +Those did not appear in the normal passing report's attachment list. New producers +write owned output files and attach paths, with distinct overview/outgoing/incoming +names. Inspect the next actual report for all pictures/geometry, not just success +counts or images buried inside a failure trace. The adapter's accepted file boundary +is unchanged. Earlier artifacts and failures are retained. diff --git a/.agents/inbox/2026-10-28-trace-fallback-needs-directional-scope.md b/.agents/inbox/2026-10-28-trace-fallback-needs-directional-scope.md new file mode 100644 index 00000000..2d0f7976 --- /dev/null +++ b/.agents/inbox/2026-10-28-trace-fallback-needs-directional-scope.md @@ -0,0 +1,68 @@ +# Trace fallback must respect direction and preserve useful screen space + +Date:2026-10-28. Core PR224, issue220. Existing guidance on control flow, +readable symbols and priority context was checked first. + +## Evidence and cause + +Final source377f5a3 passed the small priority and2000-item/5000-edge stages, but +CI40751802514 failed both nested semantic browser cases. Retained review: +https://github.com/GraphDone/GraphDone-Core/pull/224#pullrequestreview-5688631798 . +Desktop incoming Payments selected an original DEPENDS_ON bundle, then wiring +intersected it with the global unordered-pair selection. The opposite-direction +BLOCKS winner removed the only eligible incoming relationship. The phone's +additional Links row reduced the measured overview capacity to one group. +The process-evidence failure is not a third independent failing browser case; +downstream required stages did not run. + +The new component regression failed on the unchanged runtime: expected +`['fallback','reverse']`, received `['fallback']`;6 other cases passed. +Command, using an owned dependency fixture: +`vitest run src/components/SemanticGraphOverview.test.tsx --maxWorkers=1 --no-file-parallelism`. +After correction, that suite plus relationshipPriority and semanticTrace passed +19 cases in3.29s. A first command used a nonexistent vitest.config.ts and failed +before tests; it is not behavioral negative evidence. + +A first responsive candidate passed both headed nested journeys in67.27s, +zero retries/errors, but its actual390px screenshot showed Zoom to Fit covering +part of the group-count button. That candidate is visually rejected despite +the green tests. The correction retains the reserved right control lane, +uses one line for the phone group/item counts, and reduces spacing without +reducing the14/16/18px typography floors or44px touch targets. + +Final focused headed desktop/phone run passed2/2 in51.51s, zero retries, +errors or skipped cases, with the unchanged readable-text negative and strengthened +incoming-direction/control-clearance checks. Actual geometry JSON and PNGs show +two cards and one painted incoming route at both sizes: no card intersections, +unrelated crossings/overlaps, diagonal segments or small text. Partition at the +incoming trace is183 total =1 drawn +132 internal +50 external deferred; the +previously summarized incoming original is now represented in its explicit scope. +Command: `playwright test tests/e2e/graph/nested-semantic-lod.spec.ts +--project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 +--trace=retain-on-failure --reporter=json`, against an owned local API/database. +The superseded67.27s candidate and full CI failures remain retained. + +## Corrective rule + +An explicit incoming/outgoing trace re-evaluates per-original-pair fallback +among that direction's canonical external relationships, before routing or +paging. Its additional eligible originals are represented while tracing; +other global originals remain accounted for. Exiting restores the global +priority partition. Do not switch the user's level to All, reverse originals, +or use the strongest group-level bundle to suppress other member pairs. + +Maintain exact partitions for drawn, deferred, summarized, internal and pending +records. A component regression covers the opposite blocker, incoming fallback, +a lower-tier independent member pair, All, direction changes, exit and unchanged +input. The existing actual browser regression continues to require two useful +cards, a painted incoming dependency, readable text, clear routes and no writes. +It additionally checks top controls do not cover one another. Inspect screenshots; +a geometry oracle that checks only routes cannot establish control clearance. + +## Limits + +These changes concern group traces and controls, not arbitrary item obstacle +routing or larger browser capacity. Smaller screens can still honestly defer +content when finite space cannot fit two cards. A focused owned-fixture run is +not a clean-install/full-CI acceptance substitute. Earlier failures remain in +GitHub artifacts and final exact-source plus post-dev evidence is still required. diff --git a/.agents/inbox/2026-10-29-hidden-items-must-not-delay-overview.md b/.agents/inbox/2026-10-29-hidden-items-must-not-delay-overview.md new file mode 100644 index 00000000..48a82301 --- /dev/null +++ b/.agents/inbox/2026-10-29-hidden-items-must-not-delay-overview.md @@ -0,0 +1,96 @@ +# Hidden item rendering must not delay the visible overview + +## Evidence — 2026-10-29 UTC + +Actual post-merge `dev` revision `17acc25dc98978c64f87a5b0d6fb36b1833cdfa7` +failed the unchanged five-second browser zoom guard in +[CI run 40812241527](https://github.com/GraphDone/GraphDone-Core/actions/runs/40812241527). +The [retained smoke artifact](https://github.com/GraphDone/GraphDone-Core/actions/runs/40812241527/artifacts/12870958655) +contains `test-artifacts/ci-large-symbols/report.json` and its `large-symbols` +assets: `9.json` (detail zoom), `12.json` (overview zoom), `15.zip` (trace). +Recover with `gh run download 40812241527 --repo GraphDone/GraphDone-Core --name smoke-results-17acc25dc98978c64f87a5b0d6fb36b1833cdfa7`. + +The real 2,000-item / 5,000-edge case loaded in 27,088 ms. Its detail zoom took +3,745.5 ms and overview zoom took 5,119.1 ms against `< 5,000 ms`. The case failed +at 125,186 ms, below its 240-second total timeout. The new bus-inspection journey +had completed in 45,516 ms. Only four of thirteen strict reports ran; the later +zoom, final snapshot, no-write and error guards were not reached. Comprehensive +Tests run 40812241568 passed, which does not replace this failed browser guard. + +## Cause and working hypothesis + +`InteractiveGraphVisualization` clears item culling below scale 0.5, then the +zoom-end callback rebuilds item titles/metadata and forces original-edge routing +and label work. Its source groups are CSS-hidden while semantic overview owns +the display. That control flow is verified; the exact share of the 5,119 ms +attributable to hidden work is not yet measured. Previous pre-merge success is +not evidence that the actual integration passed. + +## Corrective rule + +Let the visible presentation own expensive rendering. Skip hidden item graphics +and coalesce terminal camera work, while preserving source simulation, canonical +selection, layout and camera ownership. Refresh actual cards and relationships +when returning to Items, including manual Items selection with no camera event. +Keep the five-second zoom and 45-second load guards and retain the original +failure. Validate correctness with small serial controls locally; measure the +real 2k journey and all final guards on hosted exact-revision CI. + +## Limits and tracking + +This is issue [#235](https://github.com/GraphDone/GraphDone-Core/issues/235), linked +to #231/#193. It does not claim to fix placement/routing spacing (#220/#233/#234), +change source layout ownership, or establish a timing improvement before new +measurements. Initial failed-run fingerprint: +`34c6cda2542943a3e65e610b3c92dd9f94e0f837e7f418f42f3dcb8a617337b5`. + +## Correction and local controls — 2026-10-29 UTC + +`useItemPresentation` coalesces terminal zoom work into one animation frame after +React commits the active presentation. Overview activation/deactivation is +reported in layout effects. Hidden source culling, item opacity and original-edge +routing/label work are skipped. Source simulation and camera ownership are +unchanged; the geometry scale still follows the terminal camera. Returning to +Items clears the item presentation cache and refreshes current cards, opacity, +culling and routes before paint, including manual Items without a camera event. +The scheduler cancels detached work and checks the current simulation. + +Local commands, run serially with Node 22 and one Vitest worker: + +- `npm run test --workspace=@graphdone/web -- --maxWorkers=1 src/hooks/useItemPresentation.test.tsx src/components/SemanticGraphOverview.test.tsx src/components/OverviewBusLayer.test.tsx src/lib/overviewBusHit.test.ts`: **16 passed / 4.86 s**. +- `npm run typecheck --workspace=@graphdone/web`: passed. The first local check + caught a D3 selection union on chained `attr`; individual attribute writes + corrected it before publication. +- Scoped ESLint on both presentation components and the new hook/test: **0 errors**, + 302 existing-style warnings in the large graph component. +- Playwright discovery finds the existing three 301-item desktop/phone and 2k + cases. No local browser or large fixture was launched. + +Two retained local negative controls establish the regressions: replacing the +hidden branch with unconditional item rendering fails two tests (hidden work +appears); suppressing the Items-return refresh also fails two tests (the displayed +card remains at scale 1.2/revision 0 instead of scale 0.2/revision 1). Corrected +source passes both. The hook test includes the actual `SemanticGraphOverview` +mode selector and automatic exit, and asserts current SVG text/path plus visible +host ownership. This small control is not a full-application timing measurement. + +Existing browser journeys now capture actual item-presentation pass/scale +attributes, require zero item passes during the 2k overview zoom, and check real +cards and paths after manual Items selection. These are additional assertions, +not substituted readiness or timing gates. Hosted exact-source acceptance and +independent review remain required before claiming the performance issue solved. + +## Review correction: route before restoring arrow paint + +Author self-audit and the independent reviewer identified a return-order problem +in the initial implementation (`968e956`). Arrows created during hidden overview +start at opacity zero. Applying opacity before publishing routes filters those +arrows out; newly represented priority edges have the same problem. A small +D3/JSDOM probe extracted the actual `updateZoomPresentation` callback and used +actual `edgeLOD` functions: old order retained opacity **0**, while route-first +produced opacity **1** at scale 1.2. This is a demonstrated rendering defect, not +a timing inference. The return now establishes routes before restoring opacity. +The existing desktop/phone journey additionally changes priority while hidden, +returns through the actual Items selector, and records/asserts current arrow +transforms and full opacity. Its JSON and screenshots are retained. Fresh final +source CI is required; the earlier `968e956` run is not final acceptance. diff --git a/.agents/inbox/2026-10-29-high-degree-detail-needs-local-lod.md b/.agents/inbox/2026-10-29-high-degree-detail-needs-local-lod.md new file mode 100644 index 00000000..1c713830 --- /dev/null +++ b/.agents/inbox/2026-10-29-high-degree-detail-needs-local-lod.md @@ -0,0 +1,69 @@ +# A settled graph is not useful when every neighbor is off screen + +On 2026-10-29 the owner reported that Essential/Items around EPIC2215 still showed a fan of lines with no readable endpoints. This happened in the owned301-item/370-relationship copy used for headed review, after explicit organization; the original localhost graph was not changed. + +## Evidence + +The actual headed DOM/data receipt `epic-2215-detail-failure.json` and screenshot `owned-301-organized-detail.png` show107 distinct incident neighbors,107 painted incident paths,zero visible neighbor cards andzero labels. All107 are DEPENDS_ON,106 outgoing/one incoming. The graph reports atRest/cameraReady/cameraFramed true and zero card overlaps. Its root is EPIC2215, “Lane7 — Factory: session loops, core tooling, automation hygiene”. The camera scale is1.2125732532. The canonical organizer performed11,384 edge evaluations, but put a large first ring far outside the readable viewport. These are actual owned-copy observations, not a claim that production/original data was mutated. + +Essential priority is not defective here: all107 endpoint pairs need their available fallback. Uniform global rings plus a readable zoom floor cannot make107 full cards fit at once. Readiness/no-overlap metrics and small star fixtures therefore miss the owner's actual problem. + +## Corrective contract + +Detailed Items mode needs a bounded local cut: the original focus, nearby readable original-item representatives, and semantically named overflow summaries/buses. Every painted route ends on a painted original card or summary. Replaced native cards/paths/hitboxes/arrows/labels must share presentation ownership; no offscreen wire fan may remain underneath. Preserve every directed/type/parallel original in disjoint accounting and the complete relationship browser. Missing endpoints and self relationships stay explicit. Priority suppression and spatial aggregation are separate counts. + +Projection coordinates belong to the display layer, never simulation nodes/fx/fy/position receipts. Projected cards open the original item's existing inspector; editing saved board positions explicitly returns to canonical layout first. Hold focus/page/filter through small pans and zooms; transform the accepted projection rather than rerouting every frame. Recompute live semantic memberships after actual data changes. Auto overview, manual Items, paging, Back, resize and reload need useful stable behavior. + +## Validation and limits + +The first integrated acceptance must reproduce the107-neighbor failure with visible readable original neighbors, bounded directed routes, meaningful semantic overflow, exact original accounting and no view-induced mutations. Retain the owned301 example as human evidence, a small deterministic high-degree fixture for serial local work and the existing hosted2,000-item/5,000-relationship gate for scale. Small5/9-node direct graphs must remain fully direct. Actual pointer/keyboard/original-item editing, effective paint, screen/control margins, routing work and camera continuity are required; a screenshot of a clean unrelated star cannot substitute. + +Tracked in233/PR234 and detailed wiring220. The topology/minimap foundation remains reviewable, but does not complete dense detailed LOD by itself. General arbitrary item routing and broad hosted parity remain separate from the first focused-neighborhood slice. CodeGraph ideas only, no shared library. + +## Bounded model and geometry checkpoint + +The pure `projectItemNeighborhood` model admits original refs plus live semantic summaries, caps the cut at seven cards, preserves unique deterministic paging and emits exact directed/type/parallel original IDs for bounded schematic routing. Its partitions distinguish pre-router represented candidates, priority-summarized, pending, self/internal and spatially deferred edges. A two-card phone cut retains a real neighbor; all107 originals remain accessible across pages. Directional representatives preserve incoming-parent/outgoing context when at least two real-neighbor slots fit, ranked by that direction's eligible relationship priority. The pre-correction direction cases fail3/16; corrected16/16 pass in0.385s. Group filters do not silently pull in outside-group parents or bypass suppressed reverse edges. + +The separate display-layout helper uses measured14/18px card geometry,20px viewport/control margins,64px card gutters and at most96 placement candidates. It never changes canonical positions. Its first adversarial activation check failed because unknown endpoints could create a false dense focus; activation now counts only loaded neighbors while pending originals remain in the model's denominator. Exact displayed-edge selections resolve through the existing original relationship browser without inventing group endpoints. + +Combined pure-model/layout/current-original selection and save-status controls pass39/39 in1.73s on2026-10-29, with scoped new-file lint passing. This is not browser integration acceptance. The first actual desktop integration of the owned301 case shows the focus, four real linked originals and two named semantic summaries; the first phone pass exposed wrapped controls and a persistent acknowledgment wasting the second-card area. Compact navigation plus expiry of successful acknowledgment now admits focus and one full original neighbor. Error/unsent/cleanup states remain visible. Full no-write, editing, routing geometry, pan/zoom/reload and scale acceptance are still pending. + +## First integrated owned-copy observations + +The new local renderer is separate from canonical simulation coordinates and hides the replaced native card/path/hitbox/arrow/label layers together. The existing inspector opens original IDs; local focus changes pan the canonical camera rather than save temporary display positions. Paging, direction-aware real-neighbor admission, named summaries and exact selected-edge browser scopes use the pure model. Detail mode and local focus/page/group/history preferences use the existing owner+graph layout scope. Normal successful layout acknowledgment expires after3.5s; error/unsent/receipt-recovery states remain visible. + +Actual headed checks on the owned EPIC2215 copy show four real neighbors plus two semantic groups on desktop, and the focus plus one readable real neighbor on390×844 phone. The real card opens the original inspector and returns to the local view. Focus-neighbor→Back preserves the exact camera and every canonical simulation coordinate; paging changes the real neighbor, and a clean reload retains Items mode and that neighbor, with zero page errors. These are manual owned-copy checks, not final automated acceptance. + +Two transient `onBoard is not a function` errors were observed while copying a new required-prop component before its caller during multi-file hot refresh. The mixed-version errors are retained separately. Loading the coherent full source and cleanly reloading has zero page errors. Future required-prop preview handoffs should copy while the page is on a blank owned tab, then reload; a half-updated HMR session is not a valid source acceptance run. + +The owner's subsequent phone feedback calls for a bottom menu that fits the screen. The current local footer/navigation correction is full-width, keeps equal-width app tabs and44/48px touch targets, and preserves the real-neighbor area. Exact pixel/keyboard/editor/no-write, pan/zoom, cold reload, responsive safe-area and hosted scale regressions remain required before integration. The new renderer is still draft work, and prior8698/6ba hosted results do not accept this larger composition. + +## Explicit navigation and live filters must remain recoverable + +Independent review of1669086 reproduced two navigation blockers: returning from overview search could retain an old local focus still visible on screen; removing/renaming a selected group could hide the only reset when one group remained. The pre-fix React controls fail2/3. Explicit navigation now carries a distinct sequence, including repeated navigation to an already inspected ID; opening a neighbor inspector preserves the working local focus. A persisted missing filter always exposes both the group selector and a direct reset. Four local interaction controls and three inspector controls pass; inspector/quick-editor closure restores the original projected trigger rather than a hidden native SVG trigger. The quick editor anchors to the actual displayed original card. + +The phone card header/padding now uses152px for a three-line18px title, retaining44px actions and14px supporting text. A clean composed preview at390×811 has two320×152 original cards,64px between them and20px above the full-width73px footer; the original107 relationships remain explicit. The first synthetic safe-area check incorrectly assumed taller controls than the actual154px toolbar/73px footer; its failure is retained, and the corrected actual-geometry check passes alongside the navigation/inspector controls17/17. This is a shorter-viewport check, not device-specific safe-area emulation. Final browser, scale and exact-source review remain pending. + +## Consume navigation intent before remounting a graph + +A fresh independent recheck of4ee5120 reproduced another lifecycle bug: after overview navigation toB, local navigation to b0 and a keyed graph/account remount, the parent retained the old navigation request while the child forgot it had handled that request. Remount replayedB and overwrote persisted b0. Explicit requests now have a monotonically increasing sequence, are acknowledged/cleared once handled and are bound to the current owner+graph scope; a scope change clears any outstanding request. A controlled remount regression also verifies that a second explicit navigation to the same item still works after acknowledgment. This corrects navigation ownership; persisted local focus/history remain separate user preferences. + +The107-neighbor serial headed acceptance passes desktop/phone2/2 in45.6s, including an intentionally invisible-wire negative control, actual hover/keyboard original IDs, exact camera Back/reload, real original-title editing and no unexpected mutations/errors. All source geometry/metadata/edges stay unchanged. Phone tabs measure130/130/130px across390px,50px high, with no overflow; the connection dock is flush with the canvas bottom. These current small-fixture receipts do not substitute for the new dense phase in hosted2k/5k acceptance. + +## Reserve title space for the actual displayed page + +A narrow-page regression found60px card clearance where64px is required: capacity was estimated from a seven-card desktop page, while the actual phone page selected a different original with a longer three-line title. Estimating from that unrelated page could therefore under-reserve the card envelope. The local slot cell now reserves the fixed maximum152px three-line envelope; each actual painted card still uses only its measured title/type/status height. This removes an unnecessary second incident-model projection, bounds title work to the displayed cut and prevents page-dependent clearance loss. The failing60px control is retained; corrected controller geometry must pass before final acceptance. + +## Coherent local acceptance checkpoint + +On2026-10-29 the coherent108f2d3 runtime passes the two high-degree original-item journeys and four organization/save/reload/offscreen/delayed-edge cases6/6 in90.16s, one headed worker/zero retries. The bounded model/layout/selection/controller/inspector controls pass44/44 in3.26s; web types pass. Scoped lint caught an unsupported DOM callback type name in the new inspector test even though TypeScript accepted it; the test now uses an explicit timestamp callback type, with scoped lint zero errors/two preexisting inspector-any warnings. No timing gate or visual-clearance threshold was relaxed. The heavier hosted2k/5k dense-local phase, final-source review and branch/runtime/post-dev gates remain pending. + +## Acceptance must target actual native classes and compact labels + +Independent test review of108f2d3 found the native-layer guard used nonexistent `.clickable-edge`/`.edge-arrow` selectors instead of the renderer's actual `.edge-clickable`/`.arrow`. Product CSS hides their groups, but the test could miss an exposed hitbox or arrow. Use actual renderer classes, require nonempty native-family inventories and retain controlled exposure negatives before claiming all source interaction/presentation layers are hidden. API snapshots and mutation guards also do not establish unchanged in-memory simulation fields; compare actual original-node x/y/fx/fy as separate evidence. + +Comprehensive40825745043 at23c3d1f failed on bothNode22.18/24 because the existing compact mobile storage test expected “Sync paused” after the intentional footer label became “Paused”. The full accessible description still names sync. The corrected test requires exact compact “Paused”, the unchanged full accessible label and expanded “Saved locally · sync paused”. This is an expectation correction plus stronger state coverage, not a product retry or relaxed readability gate. The failure log is retained as23c3-comprehensive-failure.log. + +Independent review of the six-case PNG archive also rejected the offscreen-organization screenshot: persistence had completed, but the camera's scheduled fit had not, so the receipt had cameraReady=false and two tiny blank cards. This was an evidence timing defect, not a verified failure of the eventual product layout. That case must now wait for actual camera readiness and pass the existing painted-symbol geometry check with both original IDs/full titles before saving its final screenshot. Retain the intermediate frame and do not present database success as visual acceptance. + +The small local browser journeys now exercise the same zoom-readiness function used by the hosted scale gate. Its first actual phone control failed after15s even while the card/wire view was readable: the shared visibility predicate required both SVG bounding-box dimensions to be positive, excluding a valid vertical local wire. The corrected wire-only check accepts positive width or height, while cards still require both dimensions, readable title paint and viewport containment. The negative trace is retained aslocal-zoom-axis-negative; no product rendering or five-second CI budget changed. diff --git a/.agents/inbox/2026-11-07-fit-must-measure-presented-geometry.md b/.agents/inbox/2026-11-07-fit-must-measure-presented-geometry.md new file mode 100644 index 00000000..74904cc6 --- /dev/null +++ b/.agents/inbox/2026-11-07-fit-must-measure-presented-geometry.md @@ -0,0 +1,55 @@ +# Camera fit must measure the geometry it will present; focus follows detail + +Observed 2026-11-07 while clearing the hosted gate's release blockers for the +first alpha release. The hosted gate builds Core `dev` (fb83fec4) with its +offline/hosted settings and drives it headed at phone, landscape and desktop +viewports. Existing LOD hysteresis/focus and responsive-acceptance lessons were +searched first; this concerns camera fitting across the compact-card threshold. + +## Evidence + +- Reproduced locally against an owned offline fixture with the gate's own cases: + focus not returned to the quick-edit gear at 320x568/390x844/430x932, expand + focus not returned at 844x390, the quick editor never opening at 667x375 and + the gear "not stable" then "not visible" at 844x390. +- Attribute tracing at 390x844 after Zoom to fit showed the framing retries + alternating every 250ms: scale 0.893 with `data-detail=compact`, then 1.083 + with `data-detail=full`, repeated for seconds. Each compact pass set + `display:none` on the focused header icon and Chrome moved focus to `<body>`. +- Cause: `fitViewToNodes` measured cards with `getNodeDimensions`, i.e. the + geometry of the *current* zoom. Full cards need a scale below 1, which makes + them compact (smaller), so the next fit zooms back above 1, and so on. Its + 60px padding also forced a 320px-max card below 100% on a 390px phone. +- The new `fit-focus.spec.ts` fails on the old source with + `0.992 full, 0.886 compact, 1.083 full, 0.886 compact, ...` and a focus loss; + it passes on the corrected source (headed and headless, CI-like fonts). +- Agent approval at 320x568 measured 603px with DejaVu Sans (the CI runner's + default) after `text-xs` became 14px; it fit with narrower local fonts. The + Agents page Access/About selects were 642px wide at 844px. + +## Corrective rules + +- Fit with the geometry the cards will have at the fitted scale + (`lib/graphFit.ts`): keep full cards at 100% when they fit with a 12px margin; + otherwise fit compact geometry and stay below the threshold. The result must + not depend on the current zoom, so framing retries and resizes settle. +- When detail changes hide the keyboard-focused (`:focus-visible`) control, + move focus to the control that replaces it (header icon <-> compact card) and + return to the same icon afterwards; never hand a mouse user a focus ring. + Dialog close returns focus to the visible equivalent of the invoking control. +- Geometry probes for "what if" scales must reuse the presented layout when the + layout key matches and keep any extra cache bounded: a live resize refits + every frame. +- A state change that replaces the focused buttons (approve/reject) shows its + result above requester-supplied text and moves focus there; shrinking copy to + fit one fixture does not survive a longer user-written field. +- Check text-heavy phone layouts with the CI runner's wider default font, not + only the workstation's (`FONTCONFIG_FILE` pointing at DejaVu Sans reproduces). +- Prefer role-based or exact locators: `getByLabel('Shared notes')` also matched + the "Refresh shared notes" button. + +## Limits + +A single card wider than ~296px still cannot be shown at 100% on a 320px phone; +by design it is a compact card whose keyboard control is the card itself. +Hosted-gate parity is established only by the hosted gate's own run. diff --git a/.agents/issues/INDEX.md b/.agents/issues/INDEX.md new file mode 100644 index 00000000..abab08e3 --- /dev/null +++ b/.agents/issues/INDEX.md @@ -0,0 +1,75 @@ +# Symptom index + +| Symptom | Canonical lesson and regression | +| --- | --- | +| A dense item shows a fan of essential wires while every neighbor is off screen, despite settled/no-overlap metrics | [Detailed LOD needs a readable local cut](../inbox/2026-10-29-high-degree-detail-needs-local-lod.md); EPIC2215 owned301 failure and local-neighborhood acceptance | +| Overview has no intersections but wires hug cards/each other, or hubs are flattened into rows | [Measure clearance and organize by topology](../inbox/2026-10-28-clean-layout-needs-clearance-and-topology.md); topology/overview organization tests, real control/save journey and strict schematic clearance | +| Zoom to fit alternates compact/full cards on phones; focus returned after quick edit or expand lands on `<body>` | [Fit the presented geometry; focus follows detail](../inbox/2026-11-07-fit-must-measure-presented-geometry.md); `graphFit.test.ts`, `fit-focus.spec.ts`, `agent-approval.spec.ts` | +| Overview zoom exceeds five seconds while original item groups are hidden | [Hidden items must not delay overview](../inbox/2026-10-29-hidden-items-must-not-delay-overview.md); issue #235, retained actual-dev 2k failure | +| Overview wires cannot be hovered, do not light endpoints, or lose focus when their route disappears | [Interaction geometry and original-edge inspection](../inbox/2026-10-28-overview-wires-need-interaction-geometry.md); `OverviewBusLayer`, semantic LOD and 2k bus inspection journeys | +| Your agent mixes setup, existing access and notes; narrow permission text clips, or unavailable access looks empty | [Separate task regions and honest availability](../inbox/2026-10-28-agent-setup-needs-separate-task-regions.md); `agent-connection` browser sequence and `AgentConnection.test.tsx` | +| Inspector code remains11–13px, nested previews have microscopic labels, long titles hide content, or inspection clicks enqueue unchanged positions | [Readable preview levels and read-only gestures](../inbox/2026-10-28-inspector-previews-need-readable-levels.md); inspector-readability browser journey and subgraph preview conservation/component cases | +| Releasing Alt during a gesture crashes or moves a node using an earlier drag's state | [Gesture mode and state must belong to one drag](../inbox/2026-10-28-drag-mode-must-survive-modifier-release.md); native fresh/prior-drag Alt-release cases in layout recovery | +| Inspector mode labels fit locally but are clipped with another system font | [Navigation must fit without shrinking its labels](../inbox/2026-10-28-inspector-controls-must-fit-system-fonts.md); wider-font fit, icon target and portal-tooltip checks in inspector-readability | +| An incoming trace loses its only dependency, or a new toolbar leaves only one group / covers another control | [Scope trace fallback and preserve measured room](../inbox/2026-10-28-trace-fallback-needs-directional-scope.md); directional component conservation and desktop/phone nested geometry/control regressions | +| Deleting a relationship updates the canvas but leaves the graph header count stale | [Refresh matching subscribed edge queries](../inbox/2026-10-28-edge-deletion-must-refresh-subscribed-counts.md); desktop/phone delete-and-fallback journeys assert the header and API agree | +| A preview still shows tiny/thin text although typography configuration matches the reviewed source | [Refresh the owned preview and verify rendered styles](../inbox/2026-10-28-preview-styles-need-rendered-verification.md); desktop/phone computed typography and 12px/400 negative in relationship-priority journeys | +| Browser assertions pass but accounting JSON/screenshots disappear from the unified report | [Materialize evidence files before collection](../inbox/2026-10-28-browser-evidence-needs-files.md); priority specs use outputPath files and final artifact audit | +| Links changes dismiss the inspector, closing a pair leaves a label hidden, or All leaves focus on BODY | [Priority controls preserve context](../inbox/2026-10-28-priority-controls-must-preserve-context.md); actual desktop/phone native-control and keyboard-return regressions | +| Browse covers layout Retry/Discard, or original relationship Edit opens Create instead | [Share control flow and stop panel click propagation](../inbox/2026-10-28-graph-controls-need-shared-flow.md); durable layout desktop/phone and exact relationship-browser editing | +| Hidden item labels acquire tiny backgrounds, reverse wires share a lane, or line-only diagnostics fail after path conversion | [Preserve conservative dimensions and shared path consumers](../inbox/2026-10-28-hidden-labels-need-valid-measurement.md); item-wire browser sequence and opposite-direction lane regression | +| A dragged offset label jumps to a nearby return wire despite a prior-leg tie-breaker | [Project onto the displayed offset path](../inbox/2026-10-28-offset-label-drag-needs-offset-path.md); close-return-leg geometry negative and correction | +| Extra graph-fixture batches can continue while timeout cleanup deletes their graph | [Keep additional edges inside tracked seeding](../inbox/2026-10-28-fixture-extras-must-share-cancellation.md); delayed edge-response owned-fixture regression | +| A clean schematic metric passes while wires are invisible, or a new relationship button makes an old member locator ambiguous | [Require actual paint and distinct controls](../inbox/2026-10-28-schematic-gates-must-see-painted-wires.md); actual overview bounds/paint/direction checks and opacity-zero negative control | +| Open bus lists disagree with live groups, failed routing searches multiply work, or hidden-link counts omit a partition | [Resolve live memberships and cap the whole invocation](../inbox/2026-10-28-bus-views-need-live-membership-and-total-budgets.md); relationship/live-update regressions, budget negative, CI text-bound failure | +| Chrome aborts with `D-Bus connection was disconnected` during strict browser acceptance | [Own the browser session bus and retain the failed run](../inbox/2026-10-28-browser-acceptance-needs-an-owned-session-bus.md); isolated local invocation, unchanged strict cases | +| Production acceptance login fails after server authentication policy tightens | [Fixtures must follow current auth policy](../inbox/2026-10-28-production-fixtures-must-follow-auth-policy.md); production challenge/seed/recreation contracts and real HTTPS acceptance | +| Main-promotion audit flags Undici through SQLite's optional native-build dependency | [Narrow consumer-verified dependency update](../inbox/2026-10-24-undici-native-build-dependency.md); [security audit index](security-audit-index.md) | +| Login accounts or generated bootstrap credentials disappear after API container recreation despite a named volume | [SQLite persistence must cover the actual npm workspace](../inbox/2026-10-23-sqlite-volume-workspace.md); `scripts/test-sqlite-container-persistence.mjs` and the production HTTP recreation hook; back up old writable-layer data before upgrade | +| Production browser login reports “Password field not found” while development quick login passes | [Development quick login does not verify manual sign-in](../inbox/2026-10-24-production-login-exercises-manual-mode.md); strict `prod-smoke` sequence and `tests/lib/auth.ts` | +| Production smoke accepts an HTTP error page, uses development credentials, or loses build failure evidence | [Production smoke must prove its own stack, identity and data](../inbox/2026-10-24-production-smoke-needs-owned-runtime-evidence.md); `scripts/test-production-stack.mjs`, `tests/lib/runner/production-stack.test.mjs` | +| Auto Back returns to a different grouping, or zoom removes the focused summary | [Restore hysteresis and reconcile owned focus](../inbox/2026-10-28-lod-return-needs-hysteresis-and-focus.md); `SemanticGraphOverview.test.tsx` and nested browser Auto-return regression | +| All browser cases pass but unified source-integrity fails after staging | [Freeze the index as well as source](../inbox/2026-10-28-freeze-index-for-strict-runs.md); commit before the gate, leave repository content and index unchanged | +| Switching accounts briefly exposes another account’s pending graph IDs after a delayed error | [Async errors must retain account ownership](../inbox/2026-10-28-async-errors-must-retain-account-ownership.md); `useLayoutRecovery.test.ts` delayed A-discard → B-load → A-return regression | +| A persisted layout replays after another tab saved, cleanup failure resends accepted work, or discard races recovery | [Persisted receipts do not authorize replay](../inbox/2026-10-28-persisted-layout-receipts-do-not-authorize-replay.md); `recoverablePositionQueue.test.ts`, `layoutPersistence.test.ts`, `layout-recovery.spec.ts`; retained stale/volatile replay and hydration-switch negatives | +| A tool upgrade's negative test exits before the intended failing task executes, or a hoisted renderer path changes | [Require executed consumer evidence](../inbox/2026-10-27-tool-upgrades-need-executed-consumer-evidence.md); installed Turbo and Mermaid contracts | +| Debian self-host scan reports unresolved OS advisories despite a clean npm inventory | [Complete Debian inventory and base maintenance](../../docs/quality/SELF_HOST_OS_TRIAGE_2026-10-28.md); per-advisory CSV and optional baseline → candidate → rollback runtime contract | +| Pruned runtime starts failing imports, retains dev tools or loses auth across container replacement | [Installed consumer and persistence proof](../inbox/2026-10-27-runtime-images-need-installed-consumer-proof.md); `tests/runtime/run-image.mjs` | +| A toolchain upgrade changes test counts, loses matcher types or drops unimported coverage | [Preserve discovery and consumer behavior](../inbox/2026-10-27-toolchain-upgrades-must-preserve-test-contracts.md); [migration evidence](../../docs/quality/TOOLCHAIN_MIGRATION_2026-10-27.md) | +| OAuth links an unverified email, changes accounts after a provider email change, or leaves an orphan user after a link failure | [Provider proof and isolated atomic linking](../inbox/2026-10-27-oauth-linking-needs-provider-proof-and-atomicity.md); `oauth-identity`, `oauth-profiles` and actual `oauth-strategies` tests | +| Login and reset links cross-use, or a reset/reactivation revives old sessions | [Bind purpose and session revision at the write](../inbox/2026-10-27-email-purpose-and-revision-are-auth-boundaries.md); real SQLite/HTTP/WebSocket `auth-link-boundary.test.ts` | +| CAPTCHA widget is visible but an immediate verification event leaves proof empty | [Attach widget listeners during commit](../inbox/2026-10-27-widget-events-need-commit-phase-listeners.md); `AuthCaptcha.test.tsx` Profiler regression | +| CI development quick login fails required proof and exhausts login attempts with NODE_ENV unset | [Declare mode in the dev entry point without overriding production](../inbox/2026-10-27-dev-launch-must-declare-mode.md); `development-startup.test.ts` and actual dev smoke | +| Arbitrary client CAPTCHA strings succeed, or direct login/signup omits proof | [Require server-issued action-bound proof and atomic consumption](../inbox/2026-10-27-captcha-proof-must-be-server-issued.md); server `captcha` / `captcha-auth` tests and `qc:captcha` | +| An old audit lists a dependency that the current advisory identifies as patched | [Reconcile advisory backports with the existing lock](../inbox/2026-10-27-reconcile-advisory-backports-with-locked-consumers.md); `selector-parser.test.mjs` | +| Phone idle-motion sample includes one camera jump immediately after rotation while physics is already at rest | [Wait for edits and camera resize readiness](../inbox/2026-10-27-motion-sampling-needs-camera-readiness.md); controlled ResizeObserver delivery in `graph-motion.spec.ts` | +| Animated zoom stops near the overview exit threshold instead of reaching its target | [Title refresh must not own the camera](../inbox/2026-10-27-lod-title-refresh-must-not-own-camera.md); frame-controlled `semantic-lod.spec.ts` negative control | +| Disposable Git fixture teardown reports ENOTEMPTY under .git after a synchronous commit | [Prevent detached fixture maintenance](../inbox/2026-10-27-disposable-git-fixtures-must-own-maintenance.md); `tests/lib/runner/unified-cli.test.mjs` | +| A router major update passes install but path handling or route UI assertions differ | [Validate installed navigation and current UI contracts](../inbox/2026-10-27-router-upgrades-need-current-consumer-contracts.md); `routerNavigation.test.tsx`, `router-navigation.spec.ts`, dependency guard | +| Status-note text lands in the title and Add remains disabled after quick edit opens | [Delayed autofocus must preserve chosen focus](../inbox/2026-10-27-delayed-autofocus-must-preserve-chosen-focus.md); `QuickEditSurface.test.tsx` controls the pending frame | +| Minimap zoom or overview inspection is overwritten while a newly loaded graph settles | [Explicit camera input must stop automatic framing](../inbox/2026-10-27-explicit-camera-input-must-stop-auto-fit.md); timer-controlled `semantic-lod.spec.ts` negative control | +| Owned preview login fails before fixture setup and its API listener is absent | [Check service health before classifying the UI failure](../inbox/2026-10-28-overview-wires-need-interaction-geometry.md#review-controls-and-recovery-2026-10-28); retain the failed trace, recover only owned loopback services, renew owned session | +| Dense-graph browser case exhausts its timeout after slow API fixture creation; cleanup errors disappear | [Bound fixture setup separately and fail on cleanup errors](../inbox/2026-10-27-cold-lod-fixtures-need-separate-budgets.md); `tests/e2e/graph/semantic-lod.spec.ts` | +| GitHub connector can read a repo but PR creation returns `Resource not accessible by integration` | [Client-specific write permissions](../inbox/2026-10-27-github-connector-write-permissions.md); verify existing authorized access without changing credentials or policy | +| LOD inspection moves the camera but loses selection; hidden source controls or nested SVG definitions leak across renderers | [Display projections need explicit renderer ownership](../inbox/2026-10-27-lod-projections-need-renderer-ownership.md); `semantic-lod.spec.ts`, `semanticLOD.test.ts` | +| A new LOD design overlooks CodeGraph or treats an unused helper as a shipped shared engine | [CodeGraph reuse map](../../docs/architecture/codegraph-lod-reuse.md); [consumer-evidence lesson](../inbox/2026-10-27-lod-reuse-needs-live-consumer-evidence.md) | +| Dense graph loses all names at fit scale; an old LOD preview is mistaken for shipped behavior | [Dynamic graph LOD continuation](../../docs/architecture/dynamic-graph-lod.md); current state, staged delivery and explicit completion gates | +| Read-only graph inspection attempts position writes without dragging | [Guard viewing before navigation and retain attempted-write failures](../inbox/2026-10-27-live-graph-review-can-attempt-layout-writes.md); existing capture guard failed correctly; owned reproduction planned | +| A proposed progressive page merge would erase earlier nodes or drop unloaded-endpoint edges | [Snapshot replacement is not page append](../inbox/2026-10-27-snapshot-merges-are-not-page-appends.md); existing merge tests establish snapshot semantics | +| A dependency fix exists in pending main promotion but dev still fails the audit | [Backport the reviewed patch and refresh target-branch evidence](../inbox/2026-10-27-dev-undici-backport.md); PR #194 | +| Deleting an owned folder removes or orphans another owner's nested folder; a deletion destination disappears with its parent | [Deletion authority includes pre-existing descendant folders](../inbox/2026-10-23-folder-delete-legacy-hierarchy.md); `packages/server/tests/folder-authorization.test.ts` | +| Build/typecheck pass but API startup, supported-Node launcher, container or repository lint fails | [Startup and save evidence pointer](../inbox/2026-10-23-core-startup-and-save-evidence.md); [runtime validation](../../docs/quality/RUNTIME_VALIDATION_2026-10-23.md) | +| Production accepts missing/default JWT or session secrets; `.env` key differs from the captured signing key | [Startup and save evidence pointer](../inbox/2026-10-23-core-startup-and-save-evidence.md); `packages/server/tests/production-startup.test.ts`, `production-secrets.test.ts` | +| Save restores an older date/title after refetch, or a late completion refills another graph's cleared undo history | [Startup and save evidence pointer](../inbox/2026-10-23-core-startup-and-save-evidence.md); `packages/web/src/components/WorkItemDetailsModal.test.tsx` | +| PR `baseRefOid` differs from another PR or the live target branch during merge inventory | [Verify live refs and transport authority before accepting a merge](../inbox/2026-10-23-pr-base-ref-is-not-live-tip.md); compare the actual remote ref and verify the resulting merge head/tree | +| `mergePullRequest` refuses a workflow update without OAuth `workflow` scope | [Recorded API refusal and same-account SSH identity check](../inbox/2026-10-23-pr-base-ref-is-not-live-tip.md#a-workflow-scoped-api-refusal-needs-its-own-evidence); authentication alone does not prove write authorization or merge completion | +| MCP tool calls succeed but the stdio client reports JSON parse errors from startup, CPU or consistency messages | [Successful MCP calls do not prove a clean stdio protocol](../inbox/2026-10-23-mcp-stdio-diagnostics.md); `packages/mcp-server/tests/neo4j-contract.test.ts` checks real subprocess traffic and stderr diagnostics | +| `dev` has an owner-authorized admin exemption; a review-rule blocker or docs-only change tempts skipping validation | [Dev admin integration playbook](../playbooks/dev-admin-integration.md); [dated authorization and merge evidence](../inbox/2026-10-23-dev-admin-integration.md). Admin authority is not a non-author approval; verify final branch CI before closeout | +| A byte-identical dev-to-main candidate triggers unvalidated image publication or untested production persistence | [Main promotion release-gate lesson](../inbox/2026-10-23-main-promotion-release-gates.md); [main promotion #189](https://github.com/GraphDone/GraphDone-Core/issues/189) | +| CI green despite production failure; registry writes before validation; fake deployment success | [Selected production gates and disabled releases](../inbox/2026-10-23-release-gates-must-fail-closed.md) | +| A federation upgrade removes UUID findings but breaks Neo4j schema generation; an override leaves the old lock resolution | [Validate federation consumers and installed UUID paths](../inbox/2026-10-27-federation-consumers-bound-uuid-upgrades.md); `tests/lib/dependencies/uuid.test.mjs` | + +| Tiny card text, wasted space, clipped or wire-overlapped labels after zoom | [Reserve the displayed symbol footprint](../inbox/2026-10-28-readable-symbols-need-reserved-geometry.md); `graphSymbolLayout.test.ts`, `edgeRouteGeometry.test.ts`; issue #222 requires actual browser and thousands-of-item evidence | + +| Whole browser sequence reaches900s with no valid report despite retained screenshots | [Bound browser acceptance by feature](../inbox/2026-10-28-browser-sequences-need-bounded-feature-scope.md); strict sequence enrollment and evidence controls | +| A fresh unchanged-source audit reports GraphQL Scalars or Hono node-server | [Remove unused code and validate the actual installed consumer](../inbox/2026-10-28-advisories-follow-installed-consumers.md); isolated static-path negative and Neo4j scalar contract | diff --git a/.agents/issues/browser-startup-index.md b/.agents/issues/browser-startup-index.md new file mode 100644 index 00000000..630c2f59 --- /dev/null +++ b/.agents/issues/browser-startup-index.md @@ -0,0 +1,6 @@ +# Browser startup evidence symptoms + +| Symptom | Lesson and regression | +| --- | --- | +| `main` never appears and the screenshot still says GraphDone Loading; failed route is unknown | [Distinguish static shell from mounted application](../inbox/2026-10-23-browser-startup-evidence.md); labeled `button-content.spec.ts` steps and bounded diagnostics | +| Strict zero-retry acceptance has no trace, or a later suite deletes earlier trace/diagnostic files | [Retain failure evidence independently of retries](../inbox/2026-10-23-browser-startup-evidence.md); `tests/lib/runner/unified-cli.test.mjs` | diff --git a/.agents/issues/magic-link-index.md b/.agents/issues/magic-link-index.md new file mode 100644 index 00000000..15630afa --- /dev/null +++ b/.agents/issues/magic-link-index.md @@ -0,0 +1,7 @@ +# Magic-link symptom index + +| Symptom | Evidence and corrective rule | +| --- | --- | +| Login and reset links cross-use, or a reset/reactivation revives old sessions | [Bind purpose and session revision at the write](../inbox/2026-10-27-email-purpose-and-revision-are-auth-boundaries.md); real SQLite/HTTP/WebSocket `auth-link-boundary.test.ts` | +| One email token returns identity more than once under concurrent verification | [Guarded token-consumption lesson](../inbox/2026-10-23-magic-link-consumption.md); `packages/server/tests/magic-link.test.ts` | +| Token consumption fails but the caller still receives identity, or an inactive account signs in by email | [Correction and failure behavior](../../docs/quality/MAGIC_LINK_REPLAY_2026-10-23.md); real SQLite error/inactive-account regressions | diff --git a/.agents/issues/mobile-touch-index.md b/.agents/issues/mobile-touch-index.md new file mode 100644 index 00000000..22d46d30 --- /dev/null +++ b/.agents/issues/mobile-touch-index.md @@ -0,0 +1,5 @@ +# Mobile touch acceptance symptoms + +| Symptom | Lesson and regression | +| --- | --- | +| A locked-node tap opens Edit work item but acceptance waits for node-inspector; the test depends on a default tutorial node | [Verify the current touch editor and durable owned work](../inbox/2026-10-23-mobile-touch-editor.md); `tests/e2e/mobile/touch-graph.spec.ts` | diff --git a/.agents/issues/password-login-index.md b/.agents/issues/password-login-index.md new file mode 100644 index 00000000..3c7c661a --- /dev/null +++ b/.agents/issues/password-login-index.md @@ -0,0 +1,7 @@ +# Password-login failure symptom index + +| Symptom | Evidence and corrective rule | +| --- | --- | +| Browser lockout exists but direct or aliased GraphQL guesses continue | [Password-login accounting lesson](../inbox/2026-10-23-password-login-accounting.md) | +| Concurrent requests exceed a failed-attempt budget | Reserve in-flight capacity before awaiting verification; see [policy and regression evidence](../../docs/quality/PASSWORD_LOGIN_LIMITS_2026-10-23.md) | +| Successful logins reset an attacker-controlled IP's failure history | Refund successful attempts without clearing unrelated IP failures; covered by the real-SQLite resolver tests | diff --git a/.agents/issues/performance-evidence-index.md b/.agents/issues/performance-evidence-index.md new file mode 100644 index 00000000..e456a015 --- /dev/null +++ b/.agents/issues/performance-evidence-index.md @@ -0,0 +1,9 @@ +# Performance evidence symptoms + +| Symptom | Lesson and regression | +| --- | --- | +| A summary screenshot passes but selection/gestures fail, or a watched API restarts during a timing gate | [Projection ownership and stable owned-fixture validation](../inbox/2026-10-27-lod-projections-need-renderer-ownership.md); production `semantic-lod.spec.ts` | +| A completed visual capture reports attempted writes, or a fit graph has zero readable identities | [Live review write attempts](../inbox/2026-10-27-live-graph-review-can-attempt-layout-writes.md); [semantic LOD acceptance](../../docs/architecture/dynamic-graph-lod.md) | +| Partial loading is designed around complete-snapshot merge helpers | [Snapshot replacement is not page append](../inbox/2026-10-27-snapshot-merges-are-not-page-appends.md) | +| Performance gate skips empty graphs, missing metrics, or times failed requests; mobile CI passes skipped required cases | [Required gates need owned measured evidence](../inbox/2026-10-23-performance-gate-evidence.md); `tests/lib/perf/*.test.mjs`, `tests/lib/runner/unified-cli.test.mjs`, `tests/perf/graph-perf.spec.ts` | +| A placed graph renders but `window.__graphPerf` is absent, stale, or belongs to a previous graph | [First and terminal measurements must publish](../inbox/2026-10-23-performance-gate-evidence.md); the three-case required performance gate | diff --git a/.agents/issues/responsive-acceptance-index.md b/.agents/issues/responsive-acceptance-index.md new file mode 100644 index 00000000..f854dcfa --- /dev/null +++ b/.agents/issues/responsive-acceptance-index.md @@ -0,0 +1,6 @@ +# Responsive acceptance symptoms + +| Symptom | Lesson and regressions | +| --- | --- | +| Hidden desktop selector on compact landscape; locked drag; animated bounds reported as drift; fit-center mismatch | [Observe rendered controls and camera translation](../inbox/2026-10-23-responsive-acceptance.md); `button-content`, `new-user-modes`, `graph-motion`, `graph-resize` browser specs | +| Mobile dialog cannot open; hidden/visible create controls collide; a dialog unexpectedly opens after dismissing More | [Own fixtures and observe dismissal](../inbox/2026-10-23-mobile-audit-fixtures.md); `mobile/dialog-positioning` and `mobile/experience` specs | diff --git a/.agents/issues/security-audit-index.md b/.agents/issues/security-audit-index.md new file mode 100644 index 00000000..3d34564e --- /dev/null +++ b/.agents/issues/security-audit-index.md @@ -0,0 +1,22 @@ +# Security audit symptom index + +| Symptom | Evidence and corrective rule | +| --- | --- | +| Turbo or Mermaid major upgrades clear advisories but can change environment, cache, dependency placement or rendering | [Executed consumer evidence](../inbox/2026-10-27-tool-upgrades-need-executed-consumer-evidence.md); [scoped migration and negative controls](../../docs/quality/REMAINING_TOOLING_2026-10-27.md) | +| A toolchain upgrade changes test counts, loses matcher types or drops unimported coverage | [Preserve discovery and consumer behavior](../inbox/2026-10-27-toolchain-upgrades-must-preserve-test-contracts.md); [migration evidence](../../docs/quality/TOOLCHAIN_MIGRATION_2026-10-27.md) | +| OAuth email metadata loses verification or callbacks log credential-bearing redirects | [Provider proof and isolated atomic linking](../inbox/2026-10-27-oauth-linking-needs-provider-proof-and-atomicity.md); [provider/recovery contract](../../docs/quality/OAUTH_IDENTITY.md) | +| Login and reset links cross-use, or a reset/reactivation revives old sessions | [Bind purpose and session revision at the write](../inbox/2026-10-27-email-purpose-and-revision-are-auth-boundaries.md); real SQLite/HTTP/WebSocket `auth-link-boundary.test.ts` | +| CAPTCHA validates only a client string's format or skips missing GraphQL proof | [Server policy, signed action/expiry and bounded replay](../inbox/2026-10-27-captcha-proof-must-be-server-issued.md); `packages/server/tests/captcha*.test.ts`, `qc:captcha` | +| Historical CSS audit findings disappear while the lock is unchanged | [Reconcile official backport ranges with actual consumers](../inbox/2026-10-27-reconcile-advisory-backports-with-locked-consumers.md); locked-path and Tailwind/parser regressions | +| Router advisory requires a major update and older upstream navigation expectations differ | [Installed consumer and route compatibility](../inbox/2026-10-27-router-upgrades-need-current-consumer-contracts.md); every locked Router path plus desktop/phone route gates | +| Fresh production audit flags Undici through optional SQLite build tooling without a direct application import | [Trace the native-build consumer and patch its actual resolved copy](../inbox/2026-10-24-undici-native-build-dependency.md); `tests/lib/dependencies/undici.test.mjs` rejects affected locked and installed consumer versions | +| Green production audit hides development-tool findings or package counts are treated as exploit counts | [Audit scope lesson](../inbox/2026-10-23-dependency-audit-scope.md); [dated security scope](../../docs/quality/SECURITY_SCOPE_2026-10-23.md) | +| Nested lint glob stalls the process | `tests/lib/dependencies/minimatch.test.mjs`; scoped parser matcher override and bounded child-process regression | +| A fresh audit reports Zod high severity after an earlier green scan; bounded collections still walk rejected elements | [Zod consumer and bounded-validation lesson](../inbox/2026-10-23-zod-constrained-collections.md); recheck the resolved SDK dependency and use an isolated audit cache without inferring why older metadata differed | +| Browser login lockout is mistaken for server throttling; one-use email token succeeds concurrently | [Remaining authentication work](../../docs/quality/SECURITY_SCOPE_2026-10-23.md#remaining-authentication-work) | +| A federation upgrade removes UUID findings but breaks Neo4j schema generation; an override leaves the old lock resolution | [Validate federation consumers and installed UUID paths](../inbox/2026-10-27-federation-consumers-bound-uuid-upgrades.md); `tests/lib/dependencies/uuid.test.mjs` | + +Runtime image pruning and actual OS/npm inventories: [2026-10-27 consumer/persistence lesson](../inbox/2026-10-27-runtime-images-need-installed-consumer-proof.md), [image provenance and residuals](../../docs/quality/RUNTIME_IMAGE_PACKAGING_2026-10-27.md). + +| A fresh unchanged-source audit reports GraphQL Scalars or Hono node-server | [Remove unused code and validate the actual installed consumer](../inbox/2026-10-28-advisories-follow-installed-consumers.md); isolated static-path negative and Neo4j scalar contract | +| Production audit reports glob/minimatch under Tailwind's sucrase | Lock sucrase 3.35.1 (drops glob, inside Tailwind's range, no override); `tests/lib/dependencies/sucrase.test.mjs` executes Tailwind's config loader | diff --git a/.agents/playbooks/dev-admin-integration.md b/.agents/playbooks/dev-admin-integration.md new file mode 100644 index 00000000..18201506 --- /dev/null +++ b/.agents/playbooks/dev-admin-integration.md @@ -0,0 +1,75 @@ +# Integrate into dev under the owner's standing admin authorization + +The owner explicitly authorized this policy on **2026-10-23**: leave `dev`'s +administrator enforcement disabled permanently and allow agent-led admin +integration with the validation bar below. This is standing authority for +`dev`, not permission inferred from an admin role. It does not cover `main`, +releases, deployments, identity changes, broader credentials or other protection +changes. Do not re-enable the setting automatically or toggle it per merge. +Later explicit owner instructions supersede this policy. + +## Before integration + +1. Use an isolated worktree and a reviewable PR. Freeze the exact source head + and read the live `dev` ref (`git ls-remote origin refs/heads/dev` or the Git + ref API); PR `baseRefOid` is not proof of the live tip. Record both SHAs and + the complete diff. If either changes, review the resulting composition and + refresh the affected validation before merging. +2. Obtain independent agent review of the exact diff; resolve its findings. + That review is useful evidence, **not a GitHub non-author approval**. The + owner's standing authority permits the admin operation without claiming + that a missing non-author approval was supplied. +3. Pass every applicable merge gate on the intended composition: pinned clean + install, build, types, lint, unit/harness, production audit, and the relevant + browser, disposable-database, runtime/image and installer checks. Runtime, + dependency, workflow or test changes require the full relevant gates. + Inspect actual job/step results; pending, missing, skipped required, flaky or + timeout evidence is not a pass. Keep earlier failures and show the correction + and subsequent evidence; do not make a retry stand in for a fix. +4. Record exact tested revisions, commands, results, review/check links, + remaining limitations and owned-fixture cleanup in the PR. Follow + [TEST_STRATEGY.md](../../docs/quality/TEST_STRATEGY.md). Passing Core gates does + not establish hosted parity or release readiness. + +## Documentation-only reuse + +Runtime results may be reused only after independently reviewing the documents +and proving the complete change set is documentation-only. Record +`git diff --name-status <validated> <head>` and +`git diff --raw --abbrev=40 <validated> <head>`; inspect every changed path, +mode and blob against an explicit list of reviewed document paths. All other +tracked content must be byte-identical to the validated revision. A `.md` +extension or a partial path-filtered diff alone is insufficient. Any changed +runtime asset, dependency, workflow, script or test invalidates this exception. +Check links/formatting, retain the provenance proof and independently review +agent instructions as instructions. Final branch CI must still pass. + +## Merge and verify + +Prefer the native PR operation after the checks above: + +```sh +gh pr merge <PR> --repo GraphDone/GraphDone-Core --merge --admin --match-head-commit <HEAD> +``` + +Recheck the live base immediately beforehand. If the API credential specifically +refuses a workflow update for missing `workflow` scope, an **already configured, +same-account SSH transport** may perform the already-authorized normal merge. +Record the exact refusal and identity check; authentication alone proves no +write permission. Never use this fallback to defeat a repository policy or +access denial; stop if that is the reason for refusal. Verify the local merge's source/base parents and complete +tree against the reviewed composition, then use an ordinary non-forced push. +A moved base requires renewed composition review, not force. Do not change +accounts, expand scopes, rewrite history or alter protection to make it work. + +Verify the actual remote `dev` commit, ancestry and complete resulting tree; +do not rely only on a command's success or a PR badge. Unexpected parents or +content require renewed review and validation before closeout. Record the resulting +SHA and the administrative operation honestly. Remove only owned temporary +processes, fixtures and credentials while retaining sanitized evidence and +earlier failures. Wait for applicable **post-merge CI on the resulting branch +head** and inspect its results before closing the integration issue. A failure +keeps closeout open and requires a reviewed correction; never erase its evidence. + +Background: [authorization and first verified integration](../inbox/2026-10-23-dev-admin-integration.md), +[live-ref and transport lessons](../inbox/2026-10-23-pr-base-ref-is-not-live-tip.md). diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..b9b4de25 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +.git +**/node_modules +**/dist +**/coverage +**/.env +**/.env.* +**/*.db +**/*.db-* +**/*.sqlite +**/*.sqlite-* +**/*.pem +**/*.key +**/certs +**/logs +**/*.log +**/.wrangler +**/.cache +packages/server/data +test-artifacts +playwright-report +test-results +.codex +.openai + +**/*.tsbuildinfo diff --git a/.env.example b/.env.example index 6ca28ff9..48c9b82e 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,9 @@ PORT=4127 NODE_ENV=development +# Live test dashboard (optional) — `npm run dashboard` serves it on this port +DASHBOARD_PORT=3199 + # SSL/TLS Configuration SSL_ENABLED=false # Set to true to enable HTTPS/TLS encryption diff --git a/.env.vm.example b/.env.vm.example deleted file mode 100644 index 84918c82..00000000 --- a/.env.vm.example +++ /dev/null @@ -1,51 +0,0 @@ -# GraphDone VM Environment Variables -# Copy this file to .env.vm and fill in your values -# These variables can be used to configure VM launches - -# Tailscale Configuration -# Get your auth key from: https://login.tailscale.com/admin/settings/keys -# IMPORTANT: Use ephemeral keys for security -TAILSCALE_AUTH_KEY= - -# VM Resource Overrides -# These override values in vm.config.yml -# VM_CPUS=4 -# VM_MEMORY=8G -# VM_DISK=30G - -# Git Configuration -# VM_BRANCH=main -# VM_REPO_URL=https://github.com/GraphDone/GraphDone-Core.git - -# VM Name Override -# VM_NAME=graphdone-dev - -# Docker Registry (for private images) -# DOCKER_REGISTRY_URL= -# DOCKER_REGISTRY_USER= -# DOCKER_REGISTRY_PASSWORD= - -# Neo4j Configuration (if different from defaults) -# NEO4J_PASSWORD=graphdone_password -# NEO4J_USER=neo4j - -# Development Settings -# NODE_ENV=development -# DEBUG=graphdone:* - -# Network Configuration -# VM_BRIDGE_INTERFACE=eth0 -# VM_USE_BRIDGE=false - -# Startup Configuration -# VM_AUTO_SETUP=true -# VM_AUTO_SEED=true -# VM_RUN_ON_BOOT=true - -# Additional Mounts (comma-separated host:vm pairs) -# VM_MOUNTS=~/data:/home/ubuntu/data,~/projects:/home/ubuntu/projects - -# Cloud Provider Settings (for cloud VMs) -# CLOUD_PROVIDER=aws -# CLOUD_REGION=us-west-2 -# CLOUD_INSTANCE_TYPE=t3.xlarge diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..5d86feda --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Describe the user-visible problem and resulting behavior. + +Validation: + +- [ ] Ran the [new-user questions](../docs/quality/NEW_USER_GATE.md); linked a release ledger with PASS/FAIL/BLOCKED evidence. +- [ ] Ran `npm run qc:new-user` and inspected headed portrait, landscape and desktop results. +- [ ] For hosted changes, ran the same saved-work mode gate against D1 and checked the staged Pages artifact. +- [ ] Listed remaining unsupported behavior and release blockers; no skipped or untested journey is claimed as passing. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f50d2e25..76eb9530 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,8 @@ name: CI on: push: - branches: [main, develop] + branches: [main, dev] pull_request: - branches: [main, develop] schedule: - cron: '0 3 * * *' # nightly full prod-stack smoke workflow_dispatch: @@ -15,7 +14,7 @@ concurrency: cancel-in-progress: true env: - NODE_VERSION: '20' + NODE_VERSION: '22' jobs: # ── Static quality: lint (0 errors) + full TypeScript type check ─────────── @@ -28,16 +27,27 @@ jobs: with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - # `npm install` (not `npm ci`) dodges the npm optional-deps bug that - # leaves the platform-specific @rollup/rollup-linux-* binary missing - # (npm/cli#4828). This is why CI used to "disable" Vitest — it never - # needed disabling, just a real install. - name: Install dependencies - run: npm install --legacy-peer-deps + run: npm ci --legacy-peer-deps - name: ESLint (fails only on errors) run: npm run lint - name: TypeScript type check (all packages) run: npm run typecheck + - name: Test evidence and runner regressions + run: npm run test:unified:lib + - name: Repository root allowlist and public-content guard + run: node --test tests/toolchain/repo-root-contract.mjs tests/toolchain/public-content-contract.mjs tests/toolchain/version-contract.mjs + - name: Schematic routing visual gate + run: | + npm run report:schematic -- --out test-artifacts/ci-schematic-routing + npm run test:schematic + - name: Retain schematic fixtures and geometry metrics + if: always() + uses: actions/upload-artifact@v4 + with: + name: schematic-routing-${{ github.sha }} + path: test-artifacts/ci-schematic-routing/ + if-no-files-found: error # ── The real unit suites, one job per package, run in parallel ───────────── unit-tests: @@ -54,9 +64,9 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies - run: npm install --legacy-peer-deps + run: npm ci --legacy-peer-deps - name: Run @graphdone/${{ matrix.package }} tests - run: npm run test --workspace=@graphdone/${{ matrix.package }} + run: npm run build --workspace=@graphdone/core && npm run test --workspace=@graphdone/${{ matrix.package }} # ── MCP server against a REAL Neo4j (catches Cypher/DB drift) ────────────── # The unit suites use a mock driver; this runs the MCP GraphService's own @@ -81,6 +91,7 @@ jobs: - 7687:7687 env: RUN_DB_CONTRACT: '1' + GRAPHDONE_DISPOSABLE_NEO4J: '1' NEO4J_URI: bolt://localhost:7687 NEO4J_USER: neo4j NEO4J_PASSWORD: graphdone_password @@ -91,7 +102,7 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies - run: npm install --legacy-peer-deps + run: npm ci --legacy-peer-deps - name: Run real-Neo4j contract test run: npx vitest --run tests/neo4j-contract.test.ts working-directory: packages/mcp-server @@ -107,13 +118,13 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies - run: npm install --legacy-peer-deps + run: npm ci --legacy-peer-deps - name: Build all packages run: npm run build - name: Bundle size budget (ADAPT-8) run: npm run perf:bundle - # ── Security scan (advisory) ─────────────────────────────────────────────── + # ── Production dependency security gate ─────────────────────────────────── security-scan: name: Security Scan runs-on: ubuntu-latest @@ -124,10 +135,11 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies - run: npm install --legacy-peer-deps - - name: npm audit (advisory — does not block) - run: npm audit --audit-level moderate - continue-on-error: true + run: npm ci --legacy-peer-deps + - name: Reject high and critical production dependency advisories + env: + NPM_CONFIG_CACHE: ${{ runner.temp }}/graphdone-audit-${{ github.run_id }}-${{ github.run_attempt }} + run: npm audit --omit=dev --audit-level=high # ── THE GATE: the app works from a user's point of view ──────────────────── # Fast feedback on every PR: boots the dev stack against a Neo4j service @@ -165,11 +177,15 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies - run: npm install --legacy-peer-deps + run: npm ci --legacy-peer-deps - name: Build (dev runtime imports built @graphdone/core) run: npm run build - name: Install Playwright (chromium) run: npx playwright install --with-deps chromium + - name: Vite proxy, hot reload and offline-shell contract + run: npm run test:toolchain + - name: Schematic fixture visual acceptance + run: node tests/run-unified.mjs --sequence schematic-visual --out test-artifacts/ci-schematic-visual - name: Start GraphDone (dev stack) run: | npm run dev > /tmp/dev.log 2>&1 & @@ -179,17 +195,67 @@ jobs: - name: Seed demo data run: npm run db:seed - name: THE GATE — user smoke suite - run: npm run test:smoke + run: node tests/run-unified.mjs --sequence smoke --out test-artifacts/ci-smoke + env: + TEST_URL: http://localhost:3127 + CI: true + - name: Item wire consumer acceptance + run: xvfb-run --auto-servernum node tests/run-unified.mjs --sequence item-wires --out test-artifacts/ci-item-wires + env: + TEST_URL: http://localhost:3127 + CI: true + - name: Two thousand item headless zoom acceptance + run: node tests/run-unified.mjs --sequence large-symbols --out test-artifacts/ci-large-symbols + env: + TEST_URL: http://localhost:3127 + CI: true + - name: Agent connection and handoff acceptance + run: xvfb-run --auto-servernum node tests/run-unified.mjs --sequence agent-connection --out test-artifacts/ci-agent-connection + env: + TEST_URL: http://localhost:3127 + CI: true + - name: Inspector readability acceptance + run: xvfb-run --auto-servernum node tests/run-unified.mjs --sequence inspector-readability --out test-artifacts/ci-inspector-readability + env: + TEST_URL: http://localhost:3127 + CI: true + - name: New-user quality gate (headed under virtual display) + run: xvfb-run --auto-servernum node tests/run-unified.mjs --sequence new-user --out test-artifacts/ci-new-user + env: + TEST_URL: http://localhost:3127 + CI: true + - name: Semantic LOD acceptance + run: xvfb-run --auto-servernum node tests/run-unified.mjs --sequence semantic-lod --out test-artifacts/ci-semantic-lod + env: + TEST_URL: http://localhost:3127 + CI: true + - name: Nested semantic LOD acceptance + run: xvfb-run --auto-servernum node tests/run-unified.mjs --sequence nested-lod --out test-artifacts/ci-nested-lod + env: + TEST_URL: http://localhost:3127 + CI: true + - name: Durable layout recovery acceptance + run: xvfb-run --auto-servernum node tests/run-unified.mjs --sequence layout-recovery --out test-artifacts/ci-layout-recovery + env: + TEST_URL: http://localhost:3127 + CI: true + - name: Route compatibility acceptance + run: xvfb-run --auto-servernum node tests/run-unified.mjs --sequence router-navigation --out test-artifacts/ci-router-navigation + env: + TEST_URL: http://localhost:3127 + CI: true + - name: Mobile UI audit (layout / contrast / dialogs across every screen) + run: node tests/run-unified.mjs --sequence mobile --out test-artifacts/ci-mobile env: TEST_URL: http://localhost:3127 CI: true - name: Living-graph effects render (LIVE-*) - run: npx playwright test tests/e2e/living-graph.spec.ts --project="GraphDone-Core/dev-neo4j/chromium" --reporter=line + run: npx playwright test tests/e2e/graph/living-graph-effects.spec.ts --project="GraphDone-Core/dev-neo4j/chromium" --reporter=line env: TEST_URL: http://localhost:3127 CI: true - name: Performance budgets (ADAPT-8) - run: npm run test:perf + run: node tests/run-unified.mjs --sequence perf-budgets --out test-artifacts/ci-perf env: TEST_URL: http://localhost:3127 CI: true @@ -241,7 +307,7 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies - run: npm install --legacy-peer-deps + run: npm ci --legacy-peer-deps - name: Build run: npm run build - name: Install Playwright (chromium) @@ -263,15 +329,12 @@ jobs: path: test-artifacts/showcase/ retention-days: 14 - # ── Full production-stack validation (nginx/TLS/Docker) ───────────────────── - # Heavy (~45 min cold build), so it runs only on main pushes, the nightly - # schedule, and manual dispatch — not on every PR. Same user-smoke suite, - # plus the broader PR suite as advisory signal, against the real prod stack. prod-smoke: - name: Prod Stack Smoke (main / nightly) + name: Prod Stack Smoke runs-on: ubuntu-latest + timeout-minutes: 90 needs: [quality, unit-tests, build] - if: github.ref == 'refs/heads/main' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + if: github.ref == 'refs/heads/main' || github.base_ref == 'main' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -279,51 +342,113 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies - run: npm install --legacy-peer-deps + run: npm ci --legacy-peer-deps - name: Install Playwright (chromium) run: npx playwright install --with-deps chromium - - name: Generate development certificates - run: ./scripts/generate-dev-certs.sh - - name: Start GraphDone (production stack) - run: | - npm run docker:prod & - echo "Waiting for the stack to be healthy (cold image build can take minutes)..." - timeout 1200 bash -c 'until curl -k https://localhost:3128/health 2>/dev/null; do sleep 5; done' - echo "Stack healthy." - - name: Seed demo data - run: npm run db:seed || echo "seed skipped/failed — smoke still validates the stack" - continue-on-error: true - - name: THE GATE — user smoke suite (prod stack) - run: npm run test:smoke - env: - TEST_URL: https://localhost:3128 - CI: true - - name: PR critical suite (advisory) - run: npm run test:pr || echo "::warning::PR critical suite has known failures — tracked in docs/TESTING_AND_REFINEMENT_PLAN.md" - continue-on-error: true + - name: Validate owned production stack and persistent authentication + run: xvfb-run --auto-servernum node scripts/test-production-stack.mjs --out test-artifacts/ci-prod-stack env: - TEST_URL: https://localhost:3128 - TEST_ENV: production CI: true - - name: Upload test artifacts + - name: Upload production evidence if: always() uses: actions/upload-artifact@v4 with: name: prod-smoke-results-${{ github.sha }} path: | - test-results/ - test-artifacts/ + test-artifacts/ci-prod-stack/run-*/report.json + test-artifacts/ci-prod-stack/run-*/*.log + if-no-files-found: error + retention-days: 7 + + runtime-images: + name: Runtime Images (${{ matrix.name }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - name: api + dockerfile: packages/server/Dockerfile + target: runner + uid: '0' + - name: generic + dockerfile: deployment/Dockerfile + target: runner + uid: '1001' + - name: self-host + dockerfile: deployment/self-host/Dockerfile + target: api + uid: '1000' + services: + neo4j: + image: neo4j:5.26.12 + env: + NEO4J_AUTH: neo4j/graphdone_runtime_fixture + NEO4J_PLUGINS: '["apoc"]' + NEO4J_dbms_security_procedures_unrestricted: 'apoc.*' + options: >- + --health-cmd "cypher-shell -u neo4j -p graphdone_runtime_fixture 'RETURN 1'" + --health-interval 10s --health-timeout 5s --health-retries 30 + ports: + - 7687:7687 + env: + GRAPHDONE_DISPOSABLE_NEO4J: '1' + NEO4J_URI: bolt://127.0.0.1:7687 + NEO4J_USER: neo4j + NEO4J_PASSWORD: graphdone_runtime_fixture + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Build the final runtime image + run: | + mkdir -p test-artifacts/runtime + docker build --pull --target '${{ matrix.target }}' -f '${{ matrix.dockerfile }}' --iidfile test-artifacts/runtime/image-id . + - name: Verify installed packages, production startup, API, MCP and volume recovery + shell: bash + run: node tests/runtime/run-image.mjs "$(cat test-artifacts/runtime/image-id)" '${{ matrix.uid }}' | tee test-artifacts/runtime/contract.jsonl + - name: Record final image OS and npm inventory + shell: bash + run: | + docker save "$(cat test-artifacts/runtime/image-id)" -o "$RUNNER_TEMP/runtime-image.tar" + mkdir -p "$RUNNER_TEMP/runtime-trivy-cache" + docker run --rm --user "$(id -u):$(id -g)" \ + --mount "type=bind,source=$RUNNER_TEMP/runtime-image.tar,target=/image.tar,readonly" \ + --mount "type=bind,source=$RUNNER_TEMP/runtime-trivy-cache,target=/cache" \ + aquasec/trivy@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c \ + image --input /image.tar --cache-dir /cache --scanners vuln --list-all-pkgs --format json \ + > test-artifacts/runtime/trivy.json + cp "$RUNNER_TEMP/runtime-trivy-cache/db/metadata.json" test-artifacts/runtime/trivy-db.json + node tests/runtime/check-scan.mjs test-artifacts/runtime/trivy.json + - name: Retain image provenance and contract results + if: always() + uses: actions/upload-artifact@v4 + with: + name: runtime-image-${{ matrix.name }}-${{ github.sha }} + path: test-artifacts/runtime/ retention-days: 7 # ── Overall status ───────────────────────────────────────────────────────── ci-success: name: CI Success runs-on: ubuntu-latest - needs: [quality, unit-tests, mcp-contract, build, smoke-gate] + needs: [quality, unit-tests, mcp-contract, build, security-scan, smoke-gate, prod-smoke, runtime-images] if: always() steps: - name: Verify required jobs passed + env: + PROD_REQUIRED: ${{ github.ref == 'refs/heads/main' || github.base_ref == 'main' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} run: | + if [[ "$PROD_REQUIRED" == "true" ]]; then + [[ "${{ needs.prod-smoke.result }}" == "success" ]] || { echo "Required production gate did not pass"; exit 1; } + else + [[ "${{ needs.prod-smoke.result }}" == "skipped" ]] || { echo "Unexpected production gate state"; exit 1; } + fi + echo "Production: ${{ needs.prod-smoke.result }} (required=$PROD_REQUIRED)" echo "Quality: ${{ needs.quality.result }}" echo "Unit tests: ${{ needs.unit-tests.result }}" echo "Build: ${{ needs.build.result }}" @@ -332,7 +457,9 @@ jobs: && "${{ needs.unit-tests.result }}" == "success" \ && "${{ needs.mcp-contract.result }}" == "success" \ && "${{ needs.build.result }}" == "success" \ - && "${{ needs.smoke-gate.result }}" == "success" ]]; then + && "${{ needs.security-scan.result }}" == "success" \ + && "${{ needs.smoke-gate.result }}" == "success" \ + && "${{ needs.runtime-images.result }}" == "success" ]]; then echo "✅ CI green — lint, types, every unit suite, the build, and the user smoke gate all passed." else echo "❌ CI failed — see the failing job above." diff --git a/.github/workflows/comprehensive-tests.yml b/.github/workflows/comprehensive-tests.yml index c6d1b83b..1f9c633d 100644 --- a/.github/workflows/comprehensive-tests.yml +++ b/.github/workflows/comprehensive-tests.yml @@ -1,15 +1,14 @@ name: Comprehensive Test Suite -# Complements ci.yml (Node 20 + full-stack smoke gate) by proving the real +# Complements ci.yml (Node 22 + full-stack smoke gate) by proving the real # unit suites also pass on the minimum supported Node version, and by # validating the installer + deployment config. Runs daily so flakes and # dependency drift surface even without a push. on: push: - branches: [main, develop] + branches: [main, dev] pull_request: - branches: [main, develop] schedule: - cron: '0 2 * * *' # daily 02:00 UTC workflow_dispatch: @@ -19,7 +18,7 @@ permissions: pull-requests: write jobs: - # Real unit suites across the supported Node range (engines: >=18) + # Real unit suites across the supported Node range (engines: >=22.18) unit-matrix: name: Unit Tests (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest @@ -27,35 +26,45 @@ jobs: strategy: fail-fast: false matrix: - node-version: ['18.x', '20.x'] + node-version: ['22.18.0', '24.x'] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - # npm install (not ci) avoids the rollup optional-deps bug (npm/cli#4828) - name: Install dependencies - run: npm install --legacy-peer-deps + run: npm ci --legacy-peer-deps - name: Run all unit suites (core, web, server, mcp-server) - run: npm run test + run: npm run test:coverage && npm run test:unified:lib - # Genuinely-real checks: the installer exists, deployment compose parses + # Genuinely-real checks: the installer installs a ref that exists and can + # fetch its code, and the deployment compose files parse installer-and-deploy-config: name: Installer & Deploy Config runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22.18.0' + - name: Installer ref and image-tag contract + run: node --test tests/toolchain/installer-contract.mjs + - name: Installer end to end in a container (clone, update, setup-script URLs) + run: sh tests/integration/installer-container.sh - name: Installation script present and executable run: | - test -f public/install.sh || { echo "::error::public/install.sh missing"; exit 1; } - test -x public/install.sh || echo "::warning::public/install.sh is not marked executable" + test -f scripts/install.sh || { echo "::error::scripts/install.sh missing"; exit 1; } + test -x scripts/install.sh || echo "::warning::scripts/install.sh is not marked executable" echo "✅ installer present" - name: Production docker compose config is valid run: | - docker compose -f deployment/docker-compose.yml config > /dev/null \ - && echo "✅ deployment/docker-compose.yml is valid" \ - || { echo "::error::deployment/docker-compose.yml failed to parse"; exit 1; } + for file in deployment/docker-compose.yml deployment/docker-compose.registry.yml; do + docker compose -f "$file" config > /dev/null \ + && echo "✅ $file is valid" \ + || { echo "::error::$file failed to parse"; exit 1; } + done # Honest PR comment derived from real job outcomes (no fabricated numbers) report: @@ -75,7 +84,7 @@ jobs: const body = [ '## 🧪 Comprehensive Test Suite', '', - `- **Unit suites (Node 18.x & 20.x)** — core, web, server, mcp-server: ${mark(unit)}`, + `- **Unit suites (Node 22.18.0 & 24.x)** — core, web, server, mcp-server: ${mark(unit)}`, `- **Installer & deploy config**: ${mark(cfg)}`, '', '_Full-stack smoke gate runs in the **CI** workflow._', diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 73d743ab..00000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,165 +0,0 @@ -name: Deploy - -on: - push: - branches: [main] - tags: ['v*'] - workflow_run: - workflows: [CI] - branches: [main] - types: [completed] - -env: - NODE_VERSION: '20' - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - deploy-staging: - name: Deploy to Staging - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' && github.event.workflow_run.conclusion == 'success' - environment: - name: staging - url: https://staging.graphdone.app - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies - run: npm install --legacy-peer-deps - - - name: Build for production - run: npm run build - env: - NODE_ENV: production - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=sha,prefix={{branch}}- - - - name: Build and push Docker images - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Deploy to staging - run: | - echo "🎭 Deploying to staging environment..." - # Add your staging deployment logic here - # For example: kubectl, docker-compose, or cloud provider CLI - echo "✅ Deployed to staging" - - deploy-production: - name: Deploy to Production - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') - environment: - name: production - url: https://graphdone.app - needs: [] - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies - run: npm install --legacy-peer-deps - - - name: Build for production - run: npm run build - env: - NODE_ENV: production - - - name: Run production tests - run: npm run test - env: - NODE_ENV: production - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=tag - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Build and push Docker images - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Deploy to production - run: | - echo "🏭 Deploying to production environment..." - # Add your production deployment logic here - # For example: kubectl, docker-compose, or cloud provider CLI - echo "✅ Deployed to production" - - - name: Create GitHub Release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: false - - notify: - name: Notify - runs-on: ubuntu-latest - needs: [deploy-staging, deploy-production] - if: always() - steps: - - name: Notification - run: | - if [[ "${{ needs.deploy-staging.result }}" == "success" ]]; then - echo "🎉 Staging deployment successful!" - fi - if [[ "${{ needs.deploy-production.result }}" == "success" ]]; then - echo "🚀 Production deployment successful!" - fi - # Add notification logic here (Slack, Discord, etc.) \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index c75e8a42..33e94ced 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,130 +1,41 @@ -name: Build and Publish Docker Images +name: Docker Build Validation on: push: - branches: - - main - tags: - - 'v*' + branches: [main] pull_request: - branches: - - main + branches: [main] workflow_dispatch: -env: - REGISTRY: ghcr.io - IMAGE_PREFIX: graphdone - IMAGE_OWNER: graphdone +permissions: + contents: read jobs: - build-and-push: + build: + name: Build ${{ matrix.image }} (no publication) runs-on: ubuntu-latest - permissions: - contents: read - packages: write - strategy: + fail-fast: false matrix: include: - image: web - context: . dockerfile: packages/web/Dockerfile - image: api - context: . dockerfile: packages/server/Dockerfile - image: neo4j - context: . dockerfile: deployment/neo4j.Dockerfile - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 + - uses: actions/checkout@v4 with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_OWNER }}/${{ env.IMAGE_PREFIX }}-${{ matrix.image }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,value=latest,enable={{is_default_branch}} - type=sha,prefix=sha- - - - name: Build and push Docker image - uses: docker/build-push-action@v5 + persist-credentials: false + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + - name: Validate both image architectures without registry writes + uses: docker/build-push-action@v6 with: - context: ${{ matrix.context }} + context: . file: ${{ matrix.dockerfile }} platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + push: false cache-from: type=gha cache-to: type=gha,mode=max - build-args: | - VITE_GRAPHQL_URL=https://localhost:4128/graphql - VITE_GRAPHQL_WS_URL=wss://localhost:4128/graphql - - build-stack: - needs: build-and-push - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Determine tag - id: tag - run: | - # Must match a tag the build-and-push job actually pushed (see the - # docker/metadata-action tags above), or imagetools can't find the - # per-image manifests. On PRs metadata pushes `pr-<number>`; on main - # it pushes `latest`. The old `${GITHUB_SHA::8}` fallback was never a - # pushed tag, so build-stack failed on every PR. - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "tag=pr-${{ github.event.number }}" >> $GITHUB_OUTPUT - elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then - echo "tag=latest" >> $GITHUB_OUTPUT - elif [[ "${{ github.ref }}" == refs/heads/* ]]; then - echo "tag=${GITHUB_REF#refs/heads/}" | sed 's/\//-/g' >> $GITHUB_OUTPUT - else - echo "tag=sha-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT - fi - - - name: Create and push manifest for stack - run: | - # Enable experimental features for manifest - export DOCKER_CLI_EXPERIMENTAL=enabled - - # Create multi-image manifest for the stack with appropriate tag - docker buildx imagetools create \ - --tag ${{ env.REGISTRY }}/${{ env.IMAGE_OWNER }}/${{ env.IMAGE_PREFIX }}-stack:${{ steps.tag.outputs.tag }} \ - ${{ env.REGISTRY }}/${{ env.IMAGE_OWNER }}/${{ env.IMAGE_PREFIX }}-web:${{ steps.tag.outputs.tag }} \ - ${{ env.REGISTRY }}/${{ env.IMAGE_OWNER }}/${{ env.IMAGE_PREFIX }}-api:${{ steps.tag.outputs.tag }} \ - ${{ env.REGISTRY }}/${{ env.IMAGE_OWNER }}/${{ env.IMAGE_PREFIX }}-neo4j:${{ steps.tag.outputs.tag }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8dcc7648..812a47cd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,225 +1,174 @@ name: Release -on: - workflow_dispatch: - inputs: - version: - description: 'Release version (e.g., 1.0.0)' - required: true - type: string - prerelease: - description: 'Is this a pre-release?' - required: false - type: boolean - default: false - -env: - NODE_VERSION: '20' +# Publishing a release: merge dev → main, wait for main's CI to go green, then +# tag that main merge commit (see docs/quality/RELEASE_GATES.md): +# git tag -a v0.4.0-alpha <main sha> -m "GraphDone v0.4.0-alpha" && git push origin v0.4.0-alpha +# Only admins can create v* tags (repository tag ruleset). This workflow refuses +# to publish unless the tag is a commit main was at, whose main-push checks +# passed and whose package versions equal the tag. +# graphdone.com is deployed from GraphDone-Cloud, not from this repository. -jobs: - validate: - name: Validate Release - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 +on: + push: + tags: ['v*.*.*'] - - name: Validate version format - run: | - if [[ ! "${{ github.event.inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+.*$ ]]; then - echo "❌ Invalid version format: ${{ github.event.inputs.version }}" - echo "Expected format: X.Y.Z or X.Y.Z-suffix" - exit 1 - fi +permissions: {} - - name: Check if tag exists - run: | - if git rev-parse "v${{ github.event.inputs.version }}" >/dev/null 2>&1; then - echo "❌ Tag v${{ github.event.inputs.version }} already exists" - exit 1 - fi +# One run per tag; never cancel a release that has started publishing. +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false - test: - name: Run Full Test Suite +jobs: + verify: + name: Verify validated source runs-on: ubuntu-latest - needs: validate - services: - postgres: - image: postgres:15-alpine - env: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: graphdone_test - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - + timeout-minutes: 10 + permissions: + contents: read + actions: read + outputs: + sha: ${{ steps.gate.outputs.sha }} + version: ${{ steps.gate.outputs.version }} steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 + - uses: actions/checkout@v4 with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies - run: npm install --legacy-peer-deps - - - name: Run linting - run: npm run lint - - - name: Run type checking - run: npm run typecheck - - - name: Run tests - run: npm run test + ref: ${{ github.sha }} + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Require a main commit with passing main-push checks and matching versions + id: gate + run: node scripts/verify-release.mjs env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/graphdone_test - - - name: Build packages - run: npm run build + GITHUB_TOKEN: ${{ github.token }} - create-release: - name: Create Release + images: + name: Publish ${{ matrix.image }} image + needs: verify runs-on: ubuntu-latest - needs: test - outputs: - release-id: ${{ steps.create-release.outputs.id }} + timeout-minutes: 120 + permissions: + contents: read + packages: write + strategy: + fail-fast: true + matrix: + include: + # Build arguments match the stack the production gate validated + # (deployment/docker-compose.yml). + - image: web + dockerfile: packages/web/Dockerfile + build-args: | + VITE_GRAPHQL_URL=https://localhost:3128/api/graphql + VITE_GRAPHQL_WS_URL=wss://localhost:3128/api/graphql + - image: api + dockerfile: packages/server/Dockerfile + build-args: '' + - image: neo4j + dockerfile: deployment/neo4j.Dockerfile + build-args: '' steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v4 + ref: ${{ needs.verify.outputs.sha }} + persist-credentials: false + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies - run: npm install --legacy-peer-deps - - - name: Update version in package.json files - run: | - # Update root package.json - npm version ${{ github.event.inputs.version }} --no-git-tag-version - - # Update package versions in all packages - for package in packages/*/package.json; do - npm version ${{ github.event.inputs.version }} --no-git-tag-version --prefix $(dirname $package) - done - - - name: Generate changelog - id: changelog - run: | - # Simple changelog generation - in practice, you might use conventional-changelog - echo "## What's Changed" > RELEASE_NOTES.md - echo "" >> RELEASE_NOTES.md - - # Get commits since last tag - LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") - if [ -n "$LAST_TAG" ]; then - git log --pretty=format:"* %s (%an)" $LAST_TAG..HEAD >> RELEASE_NOTES.md - else - git log --pretty=format:"* %s (%an)" >> RELEASE_NOTES.md - fi - - echo "" >> RELEASE_NOTES.md - echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/$LAST_TAG...v${{ github.event.inputs.version }}" >> RELEASE_NOTES.md - - - name: Build release artifacts - run: npm run build - - - name: Create distribution packages - run: | - mkdir -p dist - - # Create source archive - git archive --format=tar.gz --prefix=graphdone-${{ github.event.inputs.version }}/ HEAD > dist/graphdone-${{ github.event.inputs.version }}-source.tar.gz - - # Package built artifacts - tar -czf dist/graphdone-${{ github.event.inputs.version }}-dist.tar.gz packages/*/dist - - - name: Commit version changes - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add package.json packages/*/package.json - git commit -m "chore: bump version to ${{ github.event.inputs.version }}" - git tag -a "v${{ github.event.inputs.version }}" -m "Release v${{ github.event.inputs.version }}" - - - name: Push changes - uses: ad-m/github-push-action@master + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + # Only the version tag here; `latest` moves for all images together in + # github-release, after every image has been published. + - name: Build and push (amd64 + arm64) + id: build + uses: docker/build-push-action@v6 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - tags: true - - - name: Create GitHub Release - id: create-release - uses: actions/create-release@v1 + context: . + file: ${{ matrix.dockerfile }} + build-args: ${{ matrix.build-args }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ghcr.io/graphdone/graphdone-${{ matrix.image }}:${{ needs.verify.outputs.version }} + provenance: mode=max + sbom: true + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.revision=${{ needs.verify.outputs.sha }} + org.opencontainers.image.version=${{ needs.verify.outputs.version }} + org.opencontainers.image.licenses=MIT + - name: Record digest env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DIGEST: ${{ steps.build.outputs.digest }} + run: | + mkdir -p digests + echo "ghcr.io/graphdone/graphdone-${{ matrix.image }}@$DIGEST" > "digests/${{ matrix.image }}.txt" + - uses: actions/upload-artifact@v4 with: - tag_name: v${{ github.event.inputs.version }} - release_name: Release v${{ github.event.inputs.version }} - body_path: RELEASE_NOTES.md - draft: false - prerelease: ${{ github.event.inputs.prerelease }} + name: digest-${{ matrix.image }} + path: digests/ + if-no-files-found: error - - name: Upload source archive - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github-release: + name: Create GitHub release + needs: [verify, images] + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@v4 with: - upload_url: ${{ steps.create-release.outputs.upload_url }} - asset_path: ./dist/graphdone-${{ github.event.inputs.version }}-source.tar.gz - asset_name: graphdone-${{ github.event.inputs.version }}-source.tar.gz - asset_content_type: application/gzip - - - name: Upload distribution archive - uses: actions/upload-release-asset@v1 + ref: ${{ needs.verify.outputs.sha }} + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: '22' + # Decided now, from published releases, so a re-run cannot reuse a stale + # answer and move `latest` back to an older version. + - name: Decide whether this release becomes latest + id: latest + run: node scripts/verify-release.mjs --latest env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} + - uses: docker/login-action@v3 + if: steps.latest.outputs.latest == 'true' with: - upload_url: ${{ steps.create-release.outputs.upload_url }} - asset_path: ./dist/graphdone-${{ github.event.inputs.version }}-dist.tar.gz - asset_name: graphdone-${{ github.event.inputs.version }}-dist.tar.gz - asset_content_type: application/gzip - - deploy-release: - name: Deploy Release - runs-on: ubuntu-latest - needs: create-release - environment: - name: production - url: https://graphdone.app - steps: - - name: Trigger production deployment - run: | - echo "🚀 Triggering production deployment for v${{ github.event.inputs.version }}" - # This would trigger your production deployment pipeline - # For example, dispatching to your CD system or cloud provider - - notify-release: - name: Notify Release - runs-on: ubuntu-latest - needs: [create-release, deploy-release] - if: always() - steps: - - name: Notify success - if: needs.create-release.result == 'success' && needs.deploy-release.result == 'success' + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + - name: Move latest on all images together + if: steps.latest.outputs.latest == 'true' + env: + VERSION: ${{ needs.verify.outputs.version }} run: | - echo "🎉 Release v${{ github.event.inputs.version }} created and deployed successfully!" - # Add notification logic here (Slack, Discord, email, etc.) - - - name: Notify failure - if: needs.create-release.result == 'failure' || needs.deploy-release.result == 'failure' + for image in web api neo4j; do + docker buildx imagetools create -t "ghcr.io/graphdone/graphdone-$image:latest" "ghcr.io/graphdone/graphdone-$image:$VERSION" + done + - uses: actions/download-artifact@v4 + with: + pattern: digest-* + merge-multiple: true + path: digests + - name: Publish release notes with image digests + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} + SHA: ${{ needs.verify.outputs.sha }} + LATEST: ${{ steps.latest.outputs.latest }} run: | - echo "❌ Release v${{ github.event.inputs.version }} failed!" - # Add failure notification logic here \ No newline at end of file + { + echo "Source: \`$SHA\`" + echo + echo "**Container images** (linux/amd64 + linux/arm64, with SBOM and provenance):" + echo + for f in digests/*.txt; do echo "- \`$(cat "$f")\`"; done + echo + } > notes.md + gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --verify-tag \ + --title "GraphDone $TAG" --notes-file notes.md --generate-notes \ + --latest="$LATEST" diff --git a/.gitignore b/.gitignore index d4e8400d..2c3bb866 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Dependencies -node_modules/ +node_modules .pnp .pnp.js @@ -80,9 +80,14 @@ lerna-debug.log* # VM configuration /tmp/graphdone-cloud-init.yml cloud-init.*.yml -!cloud-init.template.yml .graphdone-cloud-init.yml +# Local agent tooling +.codex/ +.openai/ +.playwright-mcp/ +.worktrees/ + # IDE .vscode/* !.vscode/extensions.json @@ -117,4 +122,9 @@ logs/ tsconfig.tsbuildinfo # Prisma -prisma/dev.db \ No newline at end of file +prisma/dev.db +# Claude Code local worktrees/agent scratch — never commit +.claude/ + +# Private self-hosting bootstrap credentials +deployment/self-host/.env diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..8f1b168a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ +# GraphDone-Core agent instructions + +Read [CLAUDE.md](CLAUDE.md) for project and testing conventions. Preserve other +agents' worktrees and use owned disposable fixtures for database/browser work. + +The repository owner's standing authorization dated 2026-10-23 permits +administrative integration into **`dev` only** under the +[admin integration playbook](.agents/playbooks/dev-admin-integration.md). +`dev`'s “enforce rules for administrators” setting is intentionally disabled +permanently; do not automatically restore it or toggle protection for a merge. +Leave every other protection unchanged. Main, releases and deployments are +outside this authorization. + +Prefer a reviewable PR and an exact-head admin merge. Require independent agent +review, the playbook's validation/provenance checks and final branch CI. Admin +authority is not a non-author approval; report which operation actually occurred. +Do not expand credentials, change identity, force-push or rewrite history. + +Search the [symptom index](.agents/issues/INDEX.md) and existing lessons before +adding guidance. Record new incidents in `.agents/inbox/YYYY-MM-DD-<lesson>.md` +with dated evidence, cause, corrective rule and limits; link the relevant index. +Keep durable procedures in `.agents/playbooks/` and carry notes with the fix +through review. Local notes alone are not published knowledge. + +## Repository map + +| Path | What goes there | +| --- | --- | +| `packages/` | npm workspaces: `core`, `server`, `web`, `mcp-server`. Product code and its unit tests. | +| `tests/` | Cross-package suites, Playwright specs and the unified harness ([map](tests/README.md)). Single-purpose Playwright configs sit beside their specs. | +| `docs/` | Human documentation ([index](docs/README.md)). | +| `scripts/` | Runnable shell/Node scripts: stack lifecycle, VM, installer, certificates ([map](scripts/README.md)). | +| `deployment/` | Dockerfiles and compose stacks ([map](deployment/README.md)). | +| `.agents/` | Agent knowledge: incident lessons (`inbox/`), symptom indexes (`issues/`), playbooks. | +| `.github/`, `.githooks/` | CI workflows and PR template; local git hooks. | +| `README.md`, `AGENTS.md`, `CLAUDE.md`, `LICENSE` | The only root docs: overview, agent rules, project and testing conventions, licence. | +| `start`, `Makefile` | The one-command launcher and its make shortcuts. | +| root config | `package.json`, lockfile, `turbo.json`, `tsconfig.json`, `playwright.config.ts`, lint/format/ignore files, `.env*.example`. Only what a tool must find at the root. | + +Root entries are allowlisted in `tests/toolchain/repo-root-contract.mjs` (CI +"Repository root allowlist"). The list may only shrink: put a new file in the +folder above, never at the root. Inside a folder, split into domain-named +subfolders only once it is cluttered (about 15–20 files, or two or more topics +with three or more files each); give each new subfolder a short `README.md` +map linked from its parent, and move files with `git mv`. Retired files are +deleted, not parked; git history keeps them. + +## Contributor tiers + +- **Maintainer agents** act under the owner's standing authority above: claim + work, open PRs, get independent review, and integrate into `dev` per the playbook. +- **Trusted collaborators** work through gated PRs: required checks and review + apply, and they never merge their own PRs. +- **Outside forks** are evidence, not trusted code. Never approve fork workflow + runs; bring good changes in by cherry-pick (keeping the author) after a + line-by-line security review. diff --git a/CLAUDE.md b/CLAUDE.md index 349f6936..924b4fa4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,9 +2,14 @@ This file provides guidance to Claude Code when working with GraphDone-Core. +Read [AGENTS.md](AGENTS.md) for current integration authority and operational +lessons. Administrative integration into `dev` must follow the +[high-validation-bar playbook](.agents/playbooks/dev-admin-integration.md); +admin merge authority is not a non-author review approval. + ## Project Overview -GraphDone is a graph-native project management system that reimagines work coordination through dependencies and democratic prioritization rather than hierarchical assignments. Currently in active development (v0.3.1-alpha) with TLS/SSL production readiness. +GraphDone is a graph-native management system that reimagines work coordination through dependencies and democratic prioritization rather than hierarchical assignments. Currently in active alpha development (version: root `package.json`) with TLS/SSL production readiness. ## Core Philosophy @@ -106,10 +111,10 @@ git add . && git commit -m "Update version to v0.3.2-alpha" ## Testing Strategy **🔑 Authentication System for E2E Tests:** -GraphDone includes a comprehensive, battle-tested authentication system for E2E tests in `tests/helpers/auth.ts`. **This is the foundation for all E2E testing** and should be used by every test that requires user authentication. +GraphDone includes a comprehensive, battle-tested authentication system for E2E tests in `tests/lib/auth.ts`. **This is the foundation for all E2E testing** and should be used by every test that requires user authentication. ```typescript -import { login, navigateToWorkspace, TEST_USERS } from '../helpers/auth'; +import { login, navigateToWorkspace, TEST_USERS } from '../../lib/auth'; test('my feature test', async ({ page }) => { // Robust cross-browser authentication @@ -167,15 +172,43 @@ The automated testing has revealed important UI inflexibility issues that need a **🚀 Usage:** ```bash -# Run comprehensive tests with beautiful HTML report -./start test - -# View interactive report -make test-report +# Run the unified harness — dual output: HTML (with screenshots + .webm video +# clips) AND machine-parsable JSON, from the reusable tests/lib modules. +npm run test:unified # full battery (unit + e2e + report captures + cloud audit) +npm run test:unified:smoke # fast blocking subset (unit + THE GATE + a focused e2e) +npm run test:unified:open # full battery, then open the HTML report + +# Outputs land in test-artifacts/unified/: report.html + report.json (+ sequences/*.json) +make test-report # open the latest unified HTML report # or -open test-results/reports/index.html +open test-artifacts/unified/report.html ``` +**📊 Live test dashboard (the continuous "guiding light"):** +```bash +npm run dashboard # serve at http://localhost:3199 (read-only, localhost) +npm run dashboard:open # …and open the browser +make dashboard +bash scripts/install-dashboard-service.sh # ALWAYS-on: systemd --user service (auto-restart + boot) +``` +Leave it running while you work. It watches every `graphdone.unified-report/1` run +in BOTH repos (Core `test-artifacts/unified*` + Cloud `live-full-report/<stamp>`), +keeps append-only trend history that survives Core's per-run overwrite, and +**updates live over SSE** when a new run lands — performance trend charts, a runs +timeline, per-check drill-down with embedded screenshots + video clips. Zero deps +(`node:http` + SSE). It also has a **🎧 Narrated tab**: `npm run dashboard:narrate` +(`make dashboard-narrate`) renders a piper-tts walkthrough of progress (female +voice en_US-lessac-high) — test health, shipped PRs, perf trends, feature tour — +each segment's audio synced to its chart/video/screenshot. See +`tests/lib/dashboard/README.md`. + +The unified harness (`tests/run-unified.mjs` + `tests/sequences/unified.config.mjs`) +is the single entry; profiles are `smoke|pr|full|report`. The test tree is organised +as `tests/e2e/<domain>/`, `tests/diagnostics/<concern>/`, `tests/integration/`, with +all reusable helpers + harness modules under `tests/lib/`. Retired/legacy specs from the +2026-06 cleanup were removed from the tree; recover any of them from git history with +`git show fb83fec:archive/2026-06-test-cleanup/MANIFEST.md`. + **🔐 HTTPS/TLS Testing Setup (for next developer):** ```bash # 1. Generate development certificates (required for TLS tests) @@ -281,7 +314,7 @@ const GraphView = () => { }; ``` -## Current UI Evolution: Slick Dialog Revolution +## Contextual Dialog Direction (slick inline editors) GraphDone is undergoing a **major UI transformation** moving away from heavy modal dialogs toward slick, contextual, inline-style editors. This shift prioritizes **immediate feedback, minimal context switching, and delightful micro-interactions**. @@ -357,7 +390,7 @@ npm run dev TEST_URL=http://localhost:3127 npm run test:smoke ``` -`tests/e2e/user-smoke.spec.ts` sees the app exactly as a user does: login → +`tests/e2e/smoke/user-smoke.spec.ts` sees the app exactly as a user does: login → nodes AND edges render → no error chrome → no GraphQL errors reach the client → no uncaught JS errors → the grow flow works → no orphan edges in the DB. **Green unit tests do not mean the app works.** This gate exists because of a @@ -412,26 +445,28 @@ Development is driven by **[docs/USER_STORIES.md](./docs/USER_STORIES.md)**. The - [ ] **Admin page**: Apply workspace visual language consistently - [ ] **Backend Status page**: Add top bar with right-aligned status indicators -## File Organization (Recently Cleaned) +## File Organization -**Organized Structure:** -``` -artifacts/ -├── screenshots/ # All .png files moved here -├── test-reports/ # Playwright reports and test artifacts -└── certificates/ # Test certificates +The top-level map and where each kind of file goes live in [AGENTS.md](AGENTS.md#repository-map). +**Test tree:** +``` tests/ -├── e2e/ # All E2E test specs -├── helpers/ # Authentication system -└── *.js # Moved test files from root level +├── run-unified.mjs # single entry — npm run test:unified +├── sequences/ # unified.config.mjs — declarative sequences + profiles +├── e2e/<domain>/ # smoke, auth, graph, ui, a11y, mobile, responsive, api, admin, reports +├── diagnostics/<concern>/ # layout, interactions, hierarchy, inspector, physics, perf, ui +├── integration/ # infra specs (TLS, installation) — out of the default project +├── fixtures/ # shared test data +└── lib/ # reusable: auth + helpers, metrics/, audit/, reporting/, adapters/, runner/ ``` **Clean Patterns:** -- Test files belong in `tests/` directory -- Screenshots go in `artifacts/screenshots/` -- No loose files at repository root -- Certificate management consolidated +- One default `playwright.config.ts` at the repo root (auto-discovered); single-purpose configs sit beside their specs (`tests/visual/`, `tests/integration/`). The unified harness is the single test entry. +- All reusable helpers + harness modules live under `tests/lib/`; specs import `../../lib/<x>` (depth-2 domain folders). +- Capture-heavy report-only specs live in `tests/e2e/reports/` (own Playwright projects), excluded from the fast default project. +- Retired specs are deleted, not parked; git history keeps them (the 2026-06 set: `git show fb83fec:archive/2026-06-test-cleanup/MANIFEST.md`). +- No loose files at repository root: `tests/toolchain/repo-root-contract.mjs` allowlists every root entry. ## Key Implementation Guidelines @@ -454,7 +489,7 @@ export const getTypeGradientBackground = (type: WorkItemType, style: GradientSty ``` ### Authentication for Tests -Use the comprehensive auth system in `tests/helpers/auth.ts` for all E2E tests requiring login. +Use the comprehensive auth system in `tests/lib/auth.ts` for all E2E tests requiring login. ## Common Gotchas @@ -509,4 +544,4 @@ Use the comprehensive auth system in `tests/helpers/auth.ts` for all E2E tests r "You are building tools that help everyone. Take it seriously, take pride in your work, don't fake tests, we are building open source software which will help people connect with each other and work together." -The future is decentralized, free, and compassionate. This guides all architectural decisions toward democratic coordination rather than hierarchical control. \ No newline at end of file +The future is decentralized, free, and compassionate. This guides all architectural decisions toward democratic coordination rather than hierarchical control. diff --git a/CLEANUP-SAFETY-CHECK.md b/CLEANUP-SAFETY-CHECK.md deleted file mode 100644 index 3a7c0780..00000000 --- a/CLEANUP-SAFETY-CHECK.md +++ /dev/null @@ -1,76 +0,0 @@ -# Cleanup Safety Check - -## What MUST Stay in Root - -### ✅ Files that MUST remain in root: -- `Makefile` - Make commands expect it in root -- `package.json` - npm/node requirement -- `tsconfig.json` - TypeScript config -- `turbo.json` - Turbo monorepo config -- `.gitignore` - Git requirement -- `.env.example` - Convention for env templates -- `README.md` - GitHub/npm convention -- `LICENSE` - Legal/npm requirement -- `CLAUDE.md` - Project documentation -- `.eslintrc.js` - ESLint looks for it in root -- `.prettierrc` - Prettier config -- `.prettierignore` - Prettier ignore rules - -## What I Fixed After Moving - -### ✅ Fixed package.json scripts: -```json -// Before (broken): -"test:comprehensive": "node run-all-tests.js", -"test:https": "node ssl-certificate-analysis.js && node mobile-https-compatibility-test.js", - -// After (fixed): -"test:comprehensive": "node tests/run-all-tests.js", -"test:https": "node tests/ssl-certificate-analysis.js && node tests/mobile-https-compatibility-test.js", -``` - -### ✅ Restored Makefile to root: -- Initially moved to `scripts/` → broke `make` commands -- Moved back to root → now working - -## What's Safe to Move - -### ✅ Safely moved to organized directories: -- Test scripts (*.test.js, *-test.js) → `tests/` -- Test reports (*.md) → `test-results/reports/` -- Screenshots (*.png) → `artifacts/screenshots/` -- HTML reports → `test-results/` - -## Verification Commands - -Run these to verify nothing is broken: - -```bash -# Package.json scripts -npm run test:comprehensive # ✅ Works -npm run test:https # ✅ Works -npm run test:installation # ✅ Works - -# Make commands -make test-report # ✅ Works -make test-all # ✅ Works - -# Build commands -npm run build # Should work -npm run dev # Should work -``` - -## Potential Issues to Watch - -1. **Import paths in test files**: If any test files import each other, paths may need updating -2. **CI/CD pipelines**: GitHub Actions seem fine, but check other CI systems -3. **Docker**: Dockerfile COPY commands may need updates if they reference moved files -4. **Scripts**: Shell scripts that reference test files may need path updates - -## Summary - -✅ **No critical breakage** - All essential functionality preserved -✅ **Package.json updated** - Test commands now point to correct locations -✅ **Makefile restored** - Back in root where it belongs -✅ **Root is clean** - Only essential files remain -⚠️ **Monitor for issues** - Watch for any path-related errors in CI/CD or scripts \ No newline at end of file diff --git a/Makefile b/Makefile index 50d889cc..dc131f74 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # GraphDone Makefile for Test Automation # Run all tests and generate HTML report with: make test-all -.PHONY: help test test-all test-https test-e2e test-unit test-report deploy clean +.PHONY: help test test-all test-https test-e2e test-unit test-report dashboard dashboard-narrate deploy clean # Default target - show help help: @@ -23,10 +23,10 @@ help: # Run all comprehensive tests test-all: check-deploy - @echo "🧪 Running comprehensive test suite..." + @echo "🧪 Running comprehensive test suite (unified harness)..." @npm run test:comprehensive @echo "✅ Tests complete! Opening HTML report..." - @open test-results/reports/index.html + @open test-artifacts/unified/report.html # Run HTTPS/SSL tests only test-https: check-deploy @@ -48,13 +48,23 @@ test-unit: # Open test report test-report: - @if [ -f test-results/reports/index.html ]; then \ - open test-results/reports/index.html; \ - echo "📊 Opening test report in browser..."; \ + @if [ -f test-artifacts/unified/report.html ]; then \ + open test-artifacts/unified/report.html; \ + echo "📊 Opening unified test report in browser..."; \ else \ - echo "❌ No test report found. Run 'make test-all' first."; \ + echo "❌ No test report found. Run 'make test-all' (or npm run test:unified) first."; \ fi +# Live test dashboard (auto-opens browser; watches test runs + updates live) +dashboard: + @echo "📊 Starting live test dashboard at http://localhost:3199 ..." + @npm run dashboard:open + +# Generate the piper-tts narrated progress report (Narrated tab in the dashboard) +dashboard-narrate: + @echo "🎧 Generating narrated progress report (piper-tts)..." + @npm run dashboard:narrate + # Start production deployment deploy: @echo "🚀 Starting production deployment..." diff --git a/README.md b/README.md index 681f1fba..64468e67 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,23 @@ +<picture> + <source media="(prefers-color-scheme: dark)" srcset="docs/img/hero/graphdone-dark.svg"> + <img src="docs/img/hero/graphdone-light.svg" width="100%" alt="GraphDone: relationship management for agents. Work items owned by people and AI agents complete in dependency order and flow into an outcome."> +</picture> + # GraphDone -> Project management for teams who think differently. Coordinate through dependencies and outcomes, not hierarchies and top-down control. +> Management for teams who think differently. Coordinate through dependencies and outcomes, not hierarchies and top-down control. ![GraphDone's living graph — priority glow, breathing nodes, energy flowing along dependencies](./docs/graphdone_ui.png) ![License](https://img.shields.io/badge/license-MIT-blue.svg) -![Version](https://img.shields.io/badge/version-0.3.1--alpha-orange.svg) +![Version](https://img.shields.io/badge/version-0.4.0--alpha-orange.svg) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg) > 🔒 **SECURITY NOTE**: GraphDone supports both HTTP (development) and HTTPS/TLS (production) modes. For production deployment, enable TLS encryption and use production-grade authentication. See [TLS/SSL Setup Guide](./docs/tls-ssl-setup.md) for configuration details. ## What is GraphDone? -GraphDone reimagines project management as a collaborative graph where work flows through natural dependencies rather than artificial hierarchies. It's designed for high-quality individual contributors who thrive on autonomy, teams that include AI agents, and organizations ready to embrace democratic coordination. +GraphDone reimagines management as a collaborative graph where work flows through natural dependencies rather than artificial hierarchies. It's designed for high-quality individual contributors who thrive on autonomy, teams that include AI agents, and organizations ready to embrace democratic coordination. **Key Features:** - 🌊 **A living graph** - in-progress work breathes, priority glows, energy flows along dependencies, completed work celebrates. The graph feels inhabited, not diagrammed @@ -45,28 +50,32 @@ GraphDone is built on the belief that: [Read our complete philosophy →](./docs/philosophy.md) +## Public VPS hosting + +Use the [self-hosting guide](docs/deployment/SELF_HOSTING.md) and `./scripts/self-host.sh your-domain.example` for a production-oriented installation with generated credentials and private database networking. The development setup below uses demo credentials and is for local development. + ## Quick Start ### 🚀 One-Line Install (Like Ollama!) ```bash -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh | sh +curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh | sh ``` Or with wget: ```bash -wget -qO- https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh | sh +wget -qO- https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh | sh ``` **What the installer does:** 1. **Pre-flight Checks** - Validates network, disk space (5GB), download/upload speeds 2. **System Detection** - Detects platform (macOS 10.15+, Linux distros) -3. **Dependency Installation** - Installs Git, Node.js 18+, Docker if needed +3. **Dependency Installation** - Installs Git, Node.js 22.18+, Docker if needed - macOS: Uses Homebrew + OrbStack (Docker alternative) - Linux: Smart sudo authentication (works with curl/wget pipes), uses apt/dnf/yum + Docker Engine (15+ distributions supported) 4. **Code Setup** - Clones repository to `~/graphdone`, installs npm dependencies 5. **Security Config** - Generates self-signed TLS certificates for HTTPS -6. **Service Deployment** - Starts Neo4j, Redis, GraphQL API, React Web App +6. **Service Deployment** - Starts Neo4j, GraphQL API, React Web App 7. **Health Verification** - Waits for all services to be healthy (60s timeout) **Access URLs after installation:** @@ -80,18 +89,13 @@ wget -qO- https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public ```bash # Option 1: Review the script first -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh | less +curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh | less # Option 2: Download, inspect, then run -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh -o install.sh +curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh -o install.sh cat install.sh # Review the contents sh install.sh # Run after verification -# Option 3: Verify with checksums (for paranoid users) -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh.sha256 -o install.sh.sha256 -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh -o install.sh -sha256sum -c install.sh.sha256 # Verify integrity -sh install.sh ``` **What the installation script does:** @@ -114,7 +118,7 @@ sh install.sh GraphDone requires: - **Docker** - For running Neo4j graph database ([Install Docker](https://docs.docker.com/get-docker/)) - **Git** - For version control (usually pre-installed) -- **Node.js 18+** - Optional for development (auto-installed if needed) +- **Node.js 22.18+** - Optional for development (auto-installed if needed) ### Manual Installation @@ -137,7 +141,7 @@ That's it! The script will automatically: Visit **http://localhost:3127** when you see the "GraphDone is Ready!" message. -> 💡 **Don't have Node.js?** No problem! The setup script will detect this and offer to install Node.js 18 for you using nvm (Node Version Manager). +> 💡 **Don't have Node.js?** No problem! The setup script will detect this and offer to install Node.js 22 for you using nvm (Node Version Manager). ### What You Get @@ -162,8 +166,8 @@ Visit **http://localhost:3127** when you see the "GraphDone is Ready!" message. ./start quick # Manual control (advanced users) -./tools/setup.sh # One-time setup -./tools/run.sh # Start development servers +./scripts/stack/setup.sh # One-time setup +./scripts/stack/run.sh # Start development servers # Other commands ./start clean # Clean and restart fresh @@ -185,12 +189,12 @@ newgrp docker **Node.js Missing?** ```bash -./start # Will offer to install Node.js 18 automatically +./start # Will offer to install Node.js 22 automatically ``` **Port Already in Use?** ```bash -./tools/cleanup.sh # Kill any hanging processes +./scripts/stack/cleanup.sh # Kill any hanging processes ./start # Try starting again ``` diff --git a/TAILSCALE_TROUBLESHOOTING.md b/TAILSCALE_TROUBLESHOOTING.md deleted file mode 100644 index 882da151..00000000 --- a/TAILSCALE_TROUBLESHOOTING.md +++ /dev/null @@ -1,178 +0,0 @@ -# Tailscale Access Troubleshooting Guide - -## VM Status: ✅ CONFIRMED WORKING - -### Service Verification (Tested on 2025-11-13) - -**VM Details:** -- Name: `graphdone-clean` -- Tailscale IP: `100.x.y.z` -- Hostname: `graphdone-clean.your-tailnet.ts.net` - -**Services Running and Responding:** -- ✅ Web UI: `http://100.x.y.z:3127` - HTTP 200 -- ✅ GraphQL API: `http://100.x.y.z:4127/graphql` - HTTP 400 (expected - needs POST) -- ✅ Neo4j Browser: `http://100.x.y.z:7474` - HTTP 200 -- ✅ All ports listening on `0.0.0.0` (accepting external connections) -- ✅ Tailscale connected and active -- ✅ HTML content verified serving correctly - -## Client-Side Troubleshooting Steps - -Since the VM services are confirmed working, the issue is on your client device. Follow these steps: - -### Step 1: Verify Tailscale Client Status - -On your device (laptop/phone/tablet), check Tailscale is connected: - -```bash -# On macOS/Linux -tailscale status | grep graphdone-clean - -# Expected output: -# 100.x.y.z graphdone-clean graphdone-clean.your-tailnet.ts.net linux - -``` - -**Windows:** Check Tailscale system tray icon - should show green "Connected" - -**Mobile:** Open Tailscale app - should show "Connected" with green indicator - -### Step 2: Test Network Connectivity - -```bash -# Ping the VM -ping 100.x.y.z - -# Expected: 0% packet loss -``` - -If ping fails: -- Restart Tailscale on your device -- Check you're logged into the same Tailscale network (your-tailnet) -- Verify your device shows as "Connected" in Tailscale admin console - -### Step 3: Test HTTP Access - -Try accessing via IP instead of hostname: - -**Direct IP URLs:** -- Web UI: `http://100.x.y.z:3127` -- GraphQL: `http://100.x.y.z:4127/graphql` -- Neo4j: `http://100.x.y.z:7474` - -**Hostname URLs:** -- Web UI: `http://graphdone-clean.your-tailnet.ts.net:3127` -- GraphQL: `http://graphdone-clean.your-tailnet.ts.net:4127/graphql` -- Neo4j: `http://graphdone-clean.your-tailnet.ts.net:7474` - -### Step 4: Browser-Specific Checks - -**Chrome/Edge:** -- Check if browser is blocking "insecure content" (HTTP) -- Try in Incognito mode -- Check browser console (F12) for errors - -**Firefox:** -- Enhanced Tracking Protection might block connections -- Try disabling shields for this site - -**Safari:** -- Check "Prevent cross-site tracking" settings -- Try in Private Browsing mode - -### Step 5: Firewall/Security Software - -Check if your device has: -- Firewall blocking outbound connections to 100.x.y.z -- VPN software conflicting with Tailscale -- Corporate proxy/security software blocking access - -### Step 6: Use curl for Testing - -```bash -# Test from command line -curl -v http://100.x.y.z:3127 - -# Expected: HTTP 200 OK with HTML content -``` - -If curl works but browser doesn't: -- Browser extension blocking the connection -- Browser security settings too restrictive -- Try different browser - -## Common Issues and Solutions - -### Issue: "Site can't be reached" or "Connection refused" - -**Solution 1:** Restart Tailscale on your device -```bash -# macOS/Linux -sudo tailscale down && sudo tailscale up - -# Windows: Right-click Tailscale tray icon > Exit > Restart -``` - -**Solution 2:** Re-authenticate Tailscale -```bash -tailscale logout -tailscale login -``` - -### Issue: Ping works but HTTP doesn't - -**Solution:** Your browser or firewall is blocking HTTP connections -- Try curl command (Step 6) -- Disable browser security extensions temporarily -- Check system firewall settings - -### Issue: Works on one device but not another - -**Solution:** Device-specific Tailscale configuration -- Ensure both devices are on same Tailscale network -- Check Tailscale ACLs in admin console -- Verify both devices show as "Connected" - -## VM Information (For Reference) - -``` -Name: graphdone-clean -State: Running -IPv4: 10.x.y.z (Multipass internal) - 100.x.y.z (Tailscale) -Tailscale Host: graphdone-clean.your-tailnet.ts.net -Node.js: v20.19.5 -Services: All running and verified -Database: Seeded with sample data -``` - -## Login Credentials - -- **Admin**: `admin` / `graphdone` -- **Viewer**: `viewer` / `graphdone` - -## Need More Help? - -If none of the above works, run this diagnostic script on your device: - -```bash -# Comprehensive diagnostic -echo "=== Tailscale Status ===" -tailscale status | grep graphdone-clean - -echo -e "\n=== Ping Test ===" -ping -c 4 100.x.y.z - -echo -e "\n=== HTTP Test ===" -curl -v http://100.x.y.z:3127 2>&1 | head -30 - -echo -e "\n=== DNS Resolution ===" -nslookup graphdone-clean.your-tailnet.ts.net -``` - -Share the output for further troubleshooting. - ---- - -**Last Verified:** 2025-11-13 17:35 PST -**VM Status:** All services operational and responding correctly diff --git a/VM_QUICKSTART.md b/VM_QUICKSTART.md deleted file mode 100644 index 83a970d7..00000000 --- a/VM_QUICKSTART.md +++ /dev/null @@ -1,193 +0,0 @@ -# GraphDone VM Quick Start - -Run GraphDone in an isolated Multipass VM with automatic setup. - -## Installation - -```bash -# macOS -brew install --cask multipass - -# Ubuntu -sudo snap install multipass - -# Windows -# Download from https://multipass.run -``` - -## Quick Start (30 seconds) - -```bash -# 1. Launch VM with auto-generated fun name -./start vm launch -# Example output: Generated random VM name: graphdone-vm-happy-turtle-1234 - -# 2. Wait for setup to complete (2-5 minutes) -# VM will auto-configure Ubuntu, Docker, Node.js, and GraphDone - -# 3. List your VMs to see the generated name -./start vm list - -# 4. Get VM IP address (use your generated VM name) -multipass info graphdone-vm-happy-turtle-1234 | grep IPv4 - -# 5. Access GraphDone -# http://<vm-ip>:3127 - Web UI -# http://<vm-ip>:4127 - GraphQL API -# http://<vm-ip>:7474 - Neo4j Browser -``` - -## Common Commands - -```bash -# Launch VM with custom settings -./start vm launch --branch develop --cpus 8 --memory 16G - -# Connect to VM -./start vm shell - -# Stop VM -./start vm stop - -# Start VM -./start vm start - -# Delete VM -./start vm delete - -# List all VMs -./start vm list - -# Show VM info -./start vm info -``` - -## Inside the VM - -```bash -# Shortcuts available in VM shell -gd # Go to GraphDone directory -gd-start # Start GraphDone -gd-stop # Stop GraphDone -gd-status # Check status - -# Or use directly -cd ~/graphdone -./start dev -./start status -``` - -## Configuration - -Edit `vm.config.yml` to customize: - -```yaml -resources: - cpus: 4 # CPU cores - memory: 8G # RAM - disk: 30G # Disk space - -graphdone: - branch: main # Git branch to use - auto_setup: true - auto_seed: true - -tailscale: - enabled: false - auth_key: "" # Tailscale auth key -``` - -## Tailscale Integration - -1. Get auth key: https://login.tailscale.com/admin/settings/keys -2. Set in config or environment: - ```bash - export TAILSCALE_AUTH_KEY="tskey-auth-xxxxx" - ./start vm launch - ``` -3. VM joins your Tailscale network automatically - -## Multiple VMs - -Run different branches simultaneously: - -```bash -# Main branch -./start vm launch --name main-vm --branch main - -# Feature branch -./start vm launch --name feature-vm --branch feature/new-ui - -# Shell into each -./start vm shell --name main-vm -./start vm shell --name feature-vm -``` - -## Troubleshooting - -```bash -# VM not starting? -multipass list # Check status -./start vm delete # Delete and recreate -./start vm launch - -# Services not accessible? -./start vm shell # Connect to VM -cd ~/graphdone && ./start status # Check services -docker ps # Check containers - -# Cloud-init still running? -./start vm shell -cloud-init status # Check provisioning status -``` - -## Full Documentation - -See [docs/VM_SETUP.md](docs/VM_SETUP.md) for complete documentation including: -- Advanced configuration -- Network setup -- Port forwarding -- CI/CD integration -- Security best practices - -## Command Reference - -| Command | Description | -|---------|-------------| -| `./start vm launch` | Create and start new VM | -| `./start vm delete` | Delete VM and all data | -| `./start vm stop` | Stop running VM | -| `./start vm start` | Start stopped VM | -| `./start vm shell` | Open shell in VM | -| `./start vm info` | Show VM details | -| `./start vm list` | List all GraphDone VMs | - -## Options - -| Option | Description | Example | -|--------|-------------|---------| -| `--name` | VM name | `--name my-dev` | -| `--branch` | Git branch | `--branch develop` | -| `--cpus` | CPU cores | `--cpus 8` | -| `--memory` | RAM | `--memory 16G` | -| `--disk` | Disk size | `--disk 50G` | - -## Environment Variables - -Create `.env.vm` from `.env.vm.example`: - -```bash -cp .env.vm.example .env.vm -# Edit .env.vm with your settings -``` - -Key variables: -- `TAILSCALE_AUTH_KEY` - Tailscale authentication -- `VM_BRANCH` - Default Git branch -- `VM_CPUS` - Default CPU count -- `VM_MEMORY` - Default memory -- `VM_DISK` - Default disk size - ---- - -**Need help?** See [docs/VM_SETUP.md](docs/VM_SETUP.md) or run `./start vm --help` diff --git a/cloud-init.template.yml b/cloud-init.template.yml deleted file mode 100644 index 351e42a6..00000000 --- a/cloud-init.template.yml +++ /dev/null @@ -1,94 +0,0 @@ -#cloud-config -# GraphDone Multipass VM Cloud-Init Configuration -# This file is automatically generated - do not edit directly -# Edit vm.config.yml instead - -users: - - default - - name: graphdone - sudo: ALL=(ALL) NOPASSWD:ALL - shell: /bin/bash - groups: docker - ssh_authorized_keys: [] - -package_update: true -package_upgrade: true - -packages: - - build-essential - - curl - - wget - - git - - htop - - vim - - net-tools - - apt-transport-https - - ca-certificates - - software-properties-common - - gnupg - - lsb-release - {{DEV_TOOLS_PACKAGES}} - -runcmd: - # Update system - - echo "=== GraphDone VM Setup Starting ===" - - export DEBIAN_FRONTEND=noninteractive - - # Install Docker if enabled - {{DOCKER_INSTALL}} - - # Install Node.js - {{NODEJS_INSTALL}} - - # Install Tailscale if enabled - {{TAILSCALE_INSTALL}} - - # Clone GraphDone repository - - echo "=== Cloning GraphDone repository ===" - - su - ubuntu -c "git clone -b {{GRAPHDONE_BRANCH}} {{GRAPHDONE_REPO}} {{GRAPHDONE_PATH}}" - - chown -R ubuntu:ubuntu {{GRAPHDONE_PATH}} - - # Setup GraphDone - {{GRAPHDONE_SETUP}} - - # Configure services to run on boot if enabled - {{STARTUP_CONFIG}} - - # Final setup - - echo "=== GraphDone VM Setup Complete ===" - - echo "GraphDone is installed at {{GRAPHDONE_PATH}}" - - echo "To access: multipass shell {{VM_NAME}}" - - echo "Web UI will be available at: http://localhost:3127" - - echo "GraphQL API: http://localhost:4127/graphql" - - echo "Neo4j Browser: http://localhost:7474" - -write_files: - - path: /etc/profile.d/graphdone.sh - content: | - export GRAPHDONE_HOME={{GRAPHDONE_PATH}} - export PATH="$GRAPHDONE_HOME/tools:$PATH" - permissions: '0644' - - - path: /home/ubuntu/.bashrc - append: true - content: | - # GraphDone Environment - export GRAPHDONE_HOME={{GRAPHDONE_PATH}} - export PATH="$GRAPHDONE_HOME/tools:$PATH" - alias gd="cd $GRAPHDONE_HOME" - alias gd-start="cd $GRAPHDONE_HOME && ./start" - alias gd-stop="cd $GRAPHDONE_HOME && ./start stop" - alias gd-status="cd $GRAPHDONE_HOME && ./start status" - - # NVM - export NVM_DIR="$HOME/.nvm" - [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" - [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" - - {{SYSTEMD_SERVICE}} - -power_state: - mode: reboot - delay: now - message: Rebooting after GraphDone setup - condition: true diff --git a/deployment/Dockerfile b/deployment/Dockerfile index e80d9613..0355faa3 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -1,45 +1,40 @@ -# Multi-stage build for production -FROM node:18-alpine AS base - -# Install dependencies only when needed -FROM base AS deps -RUN apk add --no-cache libc6-compat +FROM node:22-alpine AS build +RUN apk add --no-cache python3 make g++ WORKDIR /app - -# Install dependencies based on the preferred package manager -COPY package.json turbo.json ./ +COPY package.json package-lock.json turbo.json tsconfig.json ./ COPY packages/core/package.json ./packages/core/ COPY packages/server/package.json ./packages/server/ +COPY packages/mcp-server/package.json ./packages/mcp-server/ COPY packages/web/package.json ./packages/web/ - -RUN npm ci --only=production - -# Build the source code -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . - -# Build all packages +RUN npm ci --legacy-peer-deps --no-audit --no-fund && npm rebuild sqlite3 --build-from-source +COPY packages ./packages RUN npm run build +RUN find packages/core/dist packages/server/dist packages/mcp-server/dist -type f \( -name '*.test.*' -o -name '*.spec.*' \) -delete -# Production image, copy all the files and run the app -FROM base AS runner +FROM node:22-alpine AS production-deps WORKDIR /app +COPY package.json package-lock.json ./ +COPY packages/core/package.json ./packages/core/ +COPY packages/server/package.json ./packages/server/ +COPY packages/mcp-server/package.json ./packages/mcp-server/ +COPY packages/web/package.json ./packages/web/ +RUN npm ci --legacy-peer-deps --omit=dev --omit=optional --ignore-scripts --no-audit --no-fund --workspace=@graphdone/core --workspace=@graphdone/server --workspace=@graphdone/mcp-server --include-workspace-root=true +COPY --from=build /app/node_modules/sqlite3/build/Release/node_sqlite3.node ./node_modules/sqlite3/build/Release/node_sqlite3.node +FROM node:22-alpine AS runner +RUN apk add --no-cache curl && rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /opt/yarn* /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack /usr/local/bin/yarn /usr/local/bin/yarnpkg /usr/local/bin/pnpm /usr/local/bin/pnpx +RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 graphdone +WORKDIR /app ENV NODE_ENV=production - -# Create non-root user -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 graphdone - -# Copy built application -COPY --from=builder --chown=graphdone:nodejs /app/packages/server/dist ./server -COPY --from=builder --chown=graphdone:nodejs /app/packages/web/dist ./web -COPY --from=deps --chown=graphdone:nodejs /app/node_modules ./node_modules - +COPY --from=production-deps /app/package.json ./package.json +COPY --from=production-deps /app/node_modules ./node_modules +COPY --from=production-deps /app/packages ./packages +COPY --from=build /app/packages/core/dist ./packages/core/dist +COPY --from=build /app/packages/server/dist ./packages/server/dist +COPY --from=build /app/packages/mcp-server/dist ./packages/mcp-server/dist +COPY --from=build /app/packages/web/dist ./web +RUN mkdir -p /app/packages/server/data && chown graphdone:nodejs /app/packages/server/data USER graphdone - -EXPOSE 4000 3000 - -CMD ["node", "server/index.js"] \ No newline at end of file +WORKDIR /app/packages/server +EXPOSE 4127 +CMD ["node", "dist/index.js"] diff --git a/deployment/README.md b/deployment/README.md new file mode 100644 index 00000000..209f57e6 --- /dev/null +++ b/deployment/README.md @@ -0,0 +1,13 @@ +# Deployment + +Container images and compose stacks. Commands run from the repo root. + +| Path | What it is | +| --- | --- | +| `Dockerfile`, `neo4j.Dockerfile` | Application and Neo4j images | +| `docker-compose.yml` | Production stack (`npm run docker:prod`) | +| `docker-compose.dev.yml` | Development stack (`npm run docker:dev`) | +| `docker-compose.http.yml`, `docker-compose.registry.yml` | Plain-HTTP and prebuilt-registry variants | +| [`self-host/`](self-host/) | Source-based self-host stack (Caddy + nginx), driven by `scripts/self-host.sh` | + +Multipass VMs are scripts, not images: see [`scripts/vm/`](../scripts/vm/README.md). diff --git a/deployment/docker-compose.dev.yml b/deployment/docker-compose.dev.yml index 5531a54f..60db46f1 100644 --- a/deployment/docker-compose.dev.yml +++ b/deployment/docker-compose.dev.yml @@ -97,7 +97,7 @@ services: condition: service_healthy volumes: - logs:/app/logs - - sqlite_auth_data:/app/data + - sqlite_auth_data:/app/packages/server/data networks: - graphdone-internal # Security configurations @@ -167,4 +167,4 @@ volumes: neo4j_data: redis_data: logs: - sqlite_auth_data: \ No newline at end of file + sqlite_auth_data: diff --git a/deployment/docker-compose.http.yml b/deployment/docker-compose.http.yml index f573c02c..3fde37a6 100644 --- a/deployment/docker-compose.http.yml +++ b/deployment/docker-compose.http.yml @@ -36,12 +36,16 @@ services: graphdone-api: container_name: graphdone-api-prod - image: gd-core-api:0.3.1-alpha + image: gd-core-api:0.4.0-alpha build: context: .. dockerfile: packages/server/Dockerfile environment: - NODE_ENV=production + - JWT_SECRET=${JWT_SECRET:-} + - SESSION_SECRET=${SESSION_SECRET:-} + - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@graphdone.local} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-} - NEO4J_URI=bolt://graphdone-neo4j:7687 - NEO4J_USER=neo4j - NEO4J_PASSWORD=graphdone_password @@ -64,7 +68,7 @@ services: volumes: - ../packages/server/.env:/app/.env - logs:/app/logs - - sqlite_auth_data:/app/data + - sqlite_auth_data:/app/packages/server/data # SSL certificates (uncomment when using HTTPS) # - ./certs:/app/certs:ro healthcheck: @@ -75,7 +79,7 @@ services: graphdone-web: container_name: graphdone-web-prod - image: gd-core-web:0.3.1-alpha + image: gd-core-web:0.4.0-alpha build: context: .. dockerfile: packages/web/Dockerfile @@ -94,4 +98,4 @@ volumes: neo4j_data: redis_data: logs: - sqlite_auth_data: \ No newline at end of file + sqlite_auth_data: diff --git a/deployment/docker-compose.registry.yml b/deployment/docker-compose.registry.yml index e4fcf61f..cc83d16c 100644 --- a/deployment/docker-compose.registry.yml +++ b/deployment/docker-compose.registry.yml @@ -3,7 +3,7 @@ name: graphdone services: graphdone-neo4j: container_name: graphdone-neo4j - image: ghcr.io/graphdone/graphdone-neo4j:fix-first-start + image: ghcr.io/graphdone/graphdone-neo4j:latest pull_policy: always privileged: true environment: @@ -39,17 +39,18 @@ services: graphdone-api: container_name: graphdone-api - image: ghcr.io/graphdone/graphdone-api:fix-first-start + image: ghcr.io/graphdone/graphdone-api:latest pull_policy: always privileged: true environment: - NODE_ENV=production + - JWT_SECRET=${JWT_SECRET:-} + - SESSION_SECRET=${SESSION_SECRET:-} - NEO4J_URI=bolt://graphdone-neo4j:7687 - NEO4J_USER=neo4j - NEO4J_PASSWORD=graphdone_password - PORT=4128 - REDIS_URL=redis://graphdone-redis:6379 - - JWT_SECRET=your-secret-key-change-in-production - SSL_ENABLED=true - SSL_KEY_PATH=/etc/ssl/private/server-key.pem - SSL_CERT_PATH=/etc/ssl/certs/server-cert.pem @@ -73,7 +74,7 @@ services: graphdone-web: container_name: graphdone-web - image: ghcr.io/graphdone/graphdone-web:fix-first-start + image: ghcr.io/graphdone/graphdone-web:latest pull_policy: always privileged: true environment: @@ -99,4 +100,4 @@ volumes: neo4j_logs: driver: local redis_data: - driver: local \ No newline at end of file + driver: local diff --git a/deployment/docker-compose.yml b/deployment/docker-compose.yml index 71eade8a..786322d3 100644 --- a/deployment/docker-compose.yml +++ b/deployment/docker-compose.yml @@ -40,12 +40,16 @@ services: graphdone-api: container_name: graphdone-api - image: gd-core-api:0.3.1-alpha + image: gd-core-api:0.4.0-alpha build: context: .. dockerfile: packages/server/Dockerfile environment: - NODE_ENV=production + - JWT_SECRET=${JWT_SECRET:-} + - SESSION_SECRET=${SESSION_SECRET:-} + - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@graphdone.local} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-} - NEO4J_URI=bolt://graphdone-neo4j:7687 - NEO4J_USER=neo4j - NEO4J_PASSWORD=graphdone_password @@ -66,7 +70,7 @@ services: volumes: - ../packages/server/.env:/app/.env - logs:/app/logs - - sqlite_auth_data:/app/data + - sqlite_auth_data:/app/packages/server/data - ./certs:/app/certs:ro # Mount SSL certificates healthcheck: test: ["CMD", "curl", "-k", "-f", "https://localhost:4128/health"] @@ -76,7 +80,7 @@ services: graphdone-web: container_name: graphdone-web - image: gd-core-web:0.3.1-alpha + image: gd-core-web:0.4.0-alpha build: context: .. dockerfile: packages/web/Dockerfile @@ -100,4 +104,4 @@ volumes: neo4j_data: redis_data: logs: - sqlite_auth_data: \ No newline at end of file + sqlite_auth_data: diff --git a/deployment/self-host/Caddyfile b/deployment/self-host/Caddyfile new file mode 100644 index 00000000..05ac6351 --- /dev/null +++ b/deployment/self-host/Caddyfile @@ -0,0 +1,4 @@ +{$GRAPHDONE_ADDRESS:80} { + encode gzip zstd + reverse_proxy web:80 +} diff --git a/deployment/self-host/Dockerfile b/deployment/self-host/Dockerfile new file mode 100644 index 00000000..0b78bc57 --- /dev/null +++ b/deployment/self-host/Dockerfile @@ -0,0 +1,42 @@ +FROM node:22-trixie-slim@sha256:8eb619e402eaede1748a3c8173bffab391592812e85136452f781741c7242ddf AS build +RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ ca-certificates && rm -rf /var/lib/apt/lists/* +WORKDIR /app +COPY package.json package-lock.json turbo.json tsconfig.json ./ +COPY packages/core/package.json ./packages/core/ +COPY packages/server/package.json ./packages/server/ +COPY packages/mcp-server/package.json ./packages/mcp-server/ +COPY packages/web/package.json ./packages/web/ +# Compile the native binding against this image's libc instead of accepting a +# prebuilt SQLite binary that may require a newer glibc than the runtime image. +RUN npm ci --legacy-peer-deps --no-audit --no-fund && npm rebuild sqlite3 --build-from-source +COPY packages ./packages +RUN npm run build -w @graphdone/core && npm run build -w @graphdone/server && npm run build -w @graphdone/mcp-server +RUN VITE_GRAPHQL_URL=/api/graphql npm run build -w @graphdone/web +RUN find packages/core/dist packages/server/dist packages/mcp-server/dist -type f \( -name '*.test.*' -o -name '*.spec.*' \) -delete +FROM node:22-trixie-slim@sha256:8eb619e402eaede1748a3c8173bffab391592812e85136452f781741c7242ddf AS production-deps +WORKDIR /app +COPY package.json package-lock.json ./ +COPY packages/core/package.json ./packages/core/ +COPY packages/server/package.json ./packages/server/ +COPY packages/mcp-server/package.json ./packages/mcp-server/ +COPY packages/web/package.json ./packages/web/ +RUN npm ci --legacy-peer-deps --omit=dev --omit=optional --ignore-scripts --no-audit --no-fund --workspace=@graphdone/core --workspace=@graphdone/server --workspace=@graphdone/mcp-server --include-workspace-root=true +COPY --from=build /app/node_modules/sqlite3/build/Release/node_sqlite3.node ./node_modules/sqlite3/build/Release/node_sqlite3.node +FROM node:22-trixie-slim@sha256:8eb619e402eaede1748a3c8173bffab391592812e85136452f781741c7242ddf AS api +ENV NODE_ENV=production +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y && rm -rf /var/lib/apt/lists/* /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /opt/yarn* /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack /usr/local/bin/yarn /usr/local/bin/yarnpkg /usr/local/bin/pnpm /usr/local/bin/pnpx +WORKDIR /app +COPY --from=production-deps /app/package.json ./package.json +COPY --from=production-deps /app/node_modules ./node_modules +COPY --from=production-deps /app/packages ./packages +COPY --from=build /app/packages/core/dist ./packages/core/dist +COPY --from=build /app/packages/server/dist ./packages/server/dist +COPY --from=build /app/packages/mcp-server/dist ./packages/mcp-server/dist +RUN mkdir -p /app/packages/server/data && chown -R node:node /app/packages/server/data +WORKDIR /app/packages/server +USER node +EXPOSE 4127 +CMD ["node","dist/index.js"] +FROM nginx:alpine AS web +COPY --from=build /app/packages/web/dist /usr/share/nginx/html +COPY deployment/self-host/nginx.conf /etc/nginx/conf.d/default.conf diff --git a/deployment/self-host/compose.yml b/deployment/self-host/compose.yml new file mode 100644 index 00000000..4dcb8a50 --- /dev/null +++ b/deployment/self-host/compose.yml @@ -0,0 +1,69 @@ +name: graphdone-selfhost +services: + proxy: + image: caddy:2-alpine + ports: ["${HTTP_PORT:-80}:80", "${HTTPS_PORT:-443}:443"] + environment: + GRAPHDONE_ADDRESS: ${GRAPHDONE_ADDRESS:-:80} + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + depends_on: [web] + restart: unless-stopped + security_opt: [no-new-privileges:true] + web: + build: + context: ../.. + dockerfile: deployment/self-host/Dockerfile + target: web + depends_on: [api] + restart: unless-stopped + security_opt: [no-new-privileges:true] + api: + build: + context: ../.. + dockerfile: deployment/self-host/Dockerfile + target: api + environment: + NODE_ENV: production + PORT: "4127" + SSL_ENABLED: "false" + NEO4J_URI: bolt://database:7687 + NEO4J_USER: neo4j + NEO4J_PASSWORD: ${NEO4J_PASSWORD:?Run scripts/self-host.sh first} + JWT_SECRET: ${JWT_SECRET:?Run scripts/self-host.sh first} + SESSION_SECRET: ${SESSION_SECRET:?Run scripts/self-host.sh first} + ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@graphdone.local} + ADMIN_PASSWORD: ${ADMIN_PASSWORD:?Run scripts/self-host.sh first} + CORS_ORIGIN: ${PUBLIC_ORIGIN:-http://localhost:8080} + CLIENT_URL: ${PUBLIC_ORIGIN:-http://localhost:8080} + TRUST_PROXY: "2" + volumes: [auth_data:/app/packages/server/data] + depends_on: + database: + condition: service_healthy + restart: unless-stopped + security_opt: [no-new-privileges:true] + database: + image: neo4j:5.26.12 + environment: + NEO4J_AUTH: neo4j/${NEO4J_PASSWORD:?Run scripts/self-host.sh first} + NEO4J_PLUGINS: '["apoc", "graph-data-science"]' + NEO4J_dbms_security_procedures_unrestricted: "gds.*,apoc.*" + NEO4J_server_memory_heap_initial__size: 256m + NEO4J_server_memory_heap_max__size: 512m + NEO4J_server_memory_pagecache_size: 256m + volumes: [graph_data:/data] + healthcheck: + test: [CMD-SHELL, 'cypher-shell -u neo4j -p "$${NEO4J_AUTH#neo4j/}" "RETURN 1" >/dev/null'] + interval: 15s + timeout: 10s + retries: 20 + restart: unless-stopped + security_opt: [no-new-privileges:true] +volumes: + auth_data: + graph_data: + caddy_data: + caddy_config: diff --git a/deployment/self-host/nginx.conf b/deployment/self-host/nginx.conf new file mode 100644 index 00000000..5d2804e2 --- /dev/null +++ b/deployment/self-host/nginx.conf @@ -0,0 +1,25 @@ +server { + listen 80; + root /usr/share/nginx/html; + index index.html; + client_max_body_size 32k; + add_header X-Content-Type-Options nosniff always; + add_header Referrer-Policy strict-origin-when-cross-origin always; + add_header X-Frame-Options SAMEORIGIN always; + location = /api/graphql { + proxy_pass http://api:4127/graphql; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location /api/ { + proxy_pass http://api:4127; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location ~ ^/(auth|health|config|mcp)(/|$) { + proxy_pass http://api:4127; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location / { try_files $uri $uri/ /index.html; } +} diff --git a/docs/README.md b/docs/README.md index 3841369d..06e98a89 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,15 +15,22 @@ Welcome to the GraphDone documentation! This directory contains comprehensive gu - [Systems Reference](./SYSTEMS.md) - **What's shipped and where it lives** — every subsystem mapped to its code, tests, and story - [Interaction Model — the friction-free contract](./design/interaction-model.md) - UX constitution: modes, exits, click budgets - [Progressive Streaming design](./design/progressive-streaming.md) - ADAPT-4: scale to huge graphs on slow links +- [Dynamic graph LOD continuation](./architecture/dynamic-graph-lod.md) - Current evidence, semantic zoom, generic AI-authored systems, staged delivery and completion gates +- [First LOD slice: validation and screenshots](./quality/DYNAMIC_LOD_VALIDATION_2026-10-27.md) - Owned desktop/phone fixtures, tested revisions and remaining gates +- [CodeGraph LOD reuse](./architecture/codegraph-lod-reuse.md) - Working techniques, source references and regression contracts to share with GraphDone - [Testing & Refinement Plan](./TESTING_AND_REFINEMENT_PLAN.md) - The never-done loop; current cycle's verification debt - [AI Agents Quickstart](./api/AI_AGENTS.md) - 5-minute MCP/GraphQL setup for agent teammates ### [Developer Guides](./guides/) - [Getting Started](./guides/getting-started.md) - Setup and first steps - [Architecture Overview](./guides/architecture-overview.md) - System design and technical decisions +- [Web / UI Architecture](./guides/web-ui-architecture.md) - View system, graph canvas, mobile shell, node inspector - [Testing Guide](../tests/README.md) - **E2E testing with robust authentication system** - [SQLite Deployment Modes](./guides/sqlite-deployment-modes.md) - Local dev vs Docker authentication storage - [User Flows](./guides/user-flows.md) - How teams actually use GraphDone +- [VM Setup (Multipass)](./guides/vm-setup.md) - Run GraphDone in a throwaway VM +- [Git Hooks](./guides/git-hooks.md) - Pre-commit / pre-push hooks +- More: [`docs/testing/`](./testing/) (test architecture, PR + OAuth testing) · [`docs/security/`](./security/) (TLS, OAuth) · [`docs/deployment/`](./deployment/) ### 🤖 AI Agents Documentation > **Start here**: [Simple AI Agent Reality Check](./simple-agent-reality.md) - **What we're actually building** @@ -50,22 +57,22 @@ Welcome to the GraphDone documentation! This directory contains comprehensive gu 1. **Setup Development Environment** ```bash - ./tools/setup.sh + ./scripts/stack/setup.sh ``` 2. **Start Development Servers** ```bash - ./tools/run.sh + ./scripts/stack/run.sh ``` 3. **Run Tests** ```bash - ./tools/test.sh + ./scripts/stack/test.sh ``` 4. **Build for Production** ```bash - ./tools/build.sh --production + ./scripts/stack/build.sh --production ``` ## 📖 Key Concepts @@ -88,4 +95,4 @@ Welcome to the GraphDone documentation! This directory contains comprehensive gu --- -*For teams who think differently* 🌐 \ No newline at end of file +*For teams who think differently* 🌐 diff --git a/docs/SYSTEMS.md b/docs/SYSTEMS.md index 129e5774..ffdd4947 100644 --- a/docs/SYSTEMS.md +++ b/docs/SYSTEMS.md @@ -10,11 +10,11 @@ | Gate | Command | What it proves | |------|---------|----------------| -| **THE GATE** ✅ | `TEST_URL=http://localhost:3127 npm run test:smoke` | The app works from a user's view: login → nodes **and** edges render → no GraphQL errors reach the client → no uncaught JS → grow + undo work → no orphan edges in the DB. See `tests/e2e/user-smoke.spec.ts`. | +| **THE GATE** ✅ | `TEST_URL=http://localhost:3127 npm run test:smoke` | The app works from a user's view: login → nodes **and** edges render → no GraphQL errors reach the client → no uncaught JS → grow + undo work → no orphan edges in the DB. See `tests/e2e/smoke/user-smoke.spec.ts`. | | Unit suites | `npm run test` (turbo) or `npm run test --workspace=@graphdone/<pkg>` | core 41 · web 103 · server 23 · mcp-server ~4740 | | Living-graph effects | `TEST_URL=http://localhost:3127 npx playwright test tests/e2e/living-graph.spec.ts` | The differentiator actually renders: breathing, glow, energy-flow, blocked-ache, hover-illumination, reduced-motion suppression. Self-seeds a fixture; gated in the smoke job. | | Performance budgets (ADAPT-8) | `npm run perf:bundle` + `TEST_URL=… npm run test:perf` | Bundle gzip ≤ 450kB; graph settles to rest, avg tick ≤ 8ms, layout drift ≤ 25px, query p95 ≤ 800ms. Gated in CI. | -| MCP real-Neo4j contract | `RUN_DB_CONTRACT=1 NEO4J_URI=… npx vitest --run tests/neo4j-contract.test.ts` (in packages/mcp-server) | MCP Cypher (node/edge lifecycle, browse) against a LIVE Neo4j — catches DB drift the mock hides. Gated in CI. | +| MCP real-Neo4j contract | `GRAPHDONE_DISPOSABLE_NEO4J=1 NEO4J_URI=bolt://127.0.0.1:<owned-port> NEO4J_USER=… NEO4J_PASSWORD=… npm run test:contract:local` (in packages/mcp-server) | MCP Cypher (node/edge lifecycle, browse) against an owned disposable loopback Neo4j. Missing configuration and non-loopback addresses fail before connecting. | | Types | `npm run typecheck` | All packages compile | | Lint | `npm run lint` | 0 errors (warnings allowed) | | Build | `npm run build` | Production build succeeds | diff --git a/docs/TESTING_AND_REFINEMENT_PLAN.md b/docs/TESTING_AND_REFINEMENT_PLAN.md index 9a29f55f..df4b2bc7 100644 --- a/docs/TESTING_AND_REFINEMENT_PLAN.md +++ b/docs/TESTING_AND_REFINEMENT_PLAN.md @@ -10,7 +10,7 @@ | Item | What to do | Exit criteria | |------|-----------|---------------| | **CI now runs the real suites** ✅ | Fixed 2026-06-13: ci.yml was disabling every Vitest suite behind a phantom "Rollup issue" and the comprehensive workflow fabricated results. Now the real core/web/server/mcp suites, the build, and THE GATE (full-stack smoke) all run and gate on every push/PR. Root cause was the `npm ci` optional-deps bug — fixed with `npm install`. | done — green on PR #37 | -| **PR-critical E2E burn-down** | The focused **smoke gate** (`test:smoke`, 3 checks) is the canonical blocking E2E and is green. The broader `test:pr` suite (~64 checks) runs as **advisory** in CI and still has known failures; migrate its specs to the `tests/helpers/api.ts` authenticated pattern (5 already done) until it can gate. | `test:pr` green, then promote to blocking | +| **PR-critical E2E burn-down** | The focused **smoke gate** (`test:smoke`, 3 checks) is the canonical blocking E2E and is green. The broader `test:pr` suite (~64 checks) runs as **advisory** in CI and still has known failures; migrate its specs to the `tests/lib/api.ts` authenticated pattern (5 already done) until it can gate. | `test:pr` green, then promote to blocking | | ~~Chaos suite: assertion failures~~ | ✅ Fixed 2026-06-10. Full suite 4,736 pass / 1 skipped / 0 assertion failures. | done | | Chaos worker-crash on local full run | Running the mcp chaos suites locally (CI skips them via `describe.skipIf(process.env.CI)`) ends with one tinypool "Worker exited unexpectedly" from `multi-perspective-chaos.test.ts` — a resource-exhaustion worker crashing, not an assertion failure (exit 1 only locally). Harden the scenario to not crash the worker, or cap its concurrency. | local `npm run test -w @graphdone/mcp-server` exits 0 | | Quality tiers on real devices | The governor is unit-tested; it has never been observed on a real phone. Throttle CPU 6x + 3G in devtools, confirm tier drops and effects strip. | Manual checklist + screen recording in PR | diff --git a/docs/USER_STORIES.md b/docs/USER_STORIES.md index c418f640..56b5765a 100644 --- a/docs/USER_STORIES.md +++ b/docs/USER_STORIES.md @@ -35,10 +35,20 @@ Why this exists: everybody hates Jira. GraphDone wins by being **alive, fast eve | ADAPT-4 | As a user with a huge graph on a slow link, I want the graph streamed by relevance (my items + center-of-gravity first, periphery progressively), so first paint is fast. | Initial query bounded (≤ 150 nodes); progressive fetch fills periphery in background; loading affordance on unexpanded frontier; TTFP < 2s on simulated 3G for 1k-node graph. | server test `server/src/__tests__/progressive-loading.test.ts`, e2e `tests/e2e/adaptive.spec.ts` | 💤 | | ADAPT-5 | As a user who upgrades conditions (wifi, plugged in), I want quality to step back up automatically, so I get the pretty version when I can afford it. | `connection.change` listener re-evaluates tier; hysteresis prevents flapping (≥ 10s between step-ups); unit-tested state machine. | `adaptiveQuality.test.ts` | ✅ | | ADAPT-6 | As a user, I want to optionally pin a quality tier in Settings (Auto / Low / High...), so I stay in control when auto guesses wrong. | Settings override persists (localStorage); "Auto" returns to detection; UI shows current effective tier. | `adaptiveQuality.test.ts`, `adaptive.spec.ts` | ✅ | -| ADAPT-7 | As a phone user, I want LOD (level-of-detail) tuned per tier — labels, icons, minimap appear/disappear by zoom *and* tier — so small screens stay readable and fast. | LOD thresholds parameterized by tier; snapshot tests per tier; no text rendering at far zoom on LOW. | `adaptiveQuality.test.ts` | 💤 | -| ADAPT-8 | As a developer, I want performance budgets enforced in CI (bundle size, TTI, fps on reference graph), so regressions get caught before merge. | CI job fails if: web bundle gzip > 450kB, 500-node graph first-render > 1.5s in headless bench, dropped-frame rate > 20% in pan bench. | `tests/perf/check-bundle-size.mjs` + `graph-perf.spec.ts`, gated in CI | ✅ | +| ADAPT-7 | As a phone user, I want LOD (level-of-detail) tuned per tier — labels, icons, minimap appear/disappear by zoom *and* tier — so small screens stay readable and fast. | LOD policy uses tier, projected size and density; individual detail is replaced by readable group names/counts at far zoom, including LOW. | `adaptiveQuality.test.ts`; `semantic-lod.spec.ts` and `nested-semantic-lod.spec.ts` cover readable overview, nested cuts, parent fallback and navigation. Per-region/tier budgets remain in the [LOD continuation](architecture/dynamic-graph-lod.md). | 🔨 | +| ADAPT-8 | As a developer, I want performance budgets enforced in CI (bundle size, TTI, fps on reference graph), so regressions get caught before merge. | Bundle gate and strict measured four-node fixture are implemented. The earlier 500-node first-render/pan targets are not established by that fixture; larger graph enforcement remains pending in the [LOD continuation](architecture/dynamic-graph-lod.md). | `tests/perf/check-bundle-size.mjs` + `tests/perf/graph-perf.spec.ts`; `tests/perf/scale-sweep.spec.ts` remains report-only | 🧪 | | ADAPT-9 | As a user with `prefers-reduced-motion`, I want all animation suppressed regardless of tier, so accessibility beats aesthetics. | Reduced-motion forces animation-free rendering at any tier; verified in unit + e2e. | `adaptiveQuality.test.ts`, `living-graph.spec.ts` | ✅ | +The [dynamic graph LOD continuation](architecture/dynamic-graph-lod.md) specifies +the current ADAPT-4/ADAPT-7 delivery path. Its stage IDs are proposed PR-sized +work, not issued GitHub tickets or completed features. + +## Schematic graph exploration + +| ID | Story | Acceptance | Test mapping | Status | +|----|-------|------------|--------------|--------| +| LOD-WIRE-1 | As a user exploring a large system, I want readable group buses and focused incoming/outgoing traces so I can follow connections without a wire ball. | Wires avoid cards/controls and unrelated buses; exact IDs/directions remain inspectable; every neighbor is reachable through bounded pages; outside connections are counted; camera/selection return and saved layout are preserved. | `tests/visual/schematic-routing.test.mjs`, `tests/visual/schematic-browser.spec.ts`, `tests/e2e/graph/nested-semantic-lod.spec.ts`, `SemanticRelationships.test.tsx`, `semanticTrace.test.ts` | 🧪 Candidate; strict CI and item wiring remain open (#219) | + ## Epic 3: Responsive Everywhere 📱💻 *Phone, tablet, PC — same living graph, appropriately shaped.* @@ -121,3 +131,28 @@ These profiles are encoded in `packages/web/src/lib/adaptiveQuality.ts` and exer | AINAT-1 | As an agent, I want MCP tools over the ontology (list sets, get defs, create typed objects/links, coverage), so domain work is fully scriptable. | MCP tools land in the same PR as each ONTO story; validation errors are machine-readable. | `mcp-server/tests/ontology-tools.test.ts` | 💤 | | AINAT-2 | As a Claude user, I want a GraphDone Skill that runs a full requirements workflow (spec doc → proposed graph → maintained trace links → coverage report), so AI does the bookkeeping while humans watch the living graph. | Skill in skills/; end-to-end demo against dev stack; humans see live updates. | skill smoke script | 💤 | | AINAT-3 | As an agent, I want get_graph_context to include the ontology section (active sets, type counts, coverage headline), so one call orients me on any domain graph. | <2.5kB response incl. ontology block. | `mcp-server/tests/graph-context.test.ts` | 💤 | + +## Live-data and responsive completion review (2026-09-07) + +See [state review](reviews/2026-09-07-state.md) for the evidence and remaining gaps. + +| ID | Story | Acceptance / evidence | Status | +| --- | --- | --- | --- | +| DATA-1 | As a contributor, I want analytics based on my selected graph so progress is trustworthy. | Actual node/status/priority/contributor data; graph selection, search/status filters, empty/error/retry states; `graphAnalytics.test.ts` and `responsive-real-workflows.spec.ts`. Historical voting metrics remain unavailable. | 🧪 | +| RESP-1a | As a phone user, I want quick editing to remain within the visible screen in either orientation. | Viewport-bounded editor/sheet, 44px controls, one-tap entry, Escape/click-away and focus handling; responsive persistence journey. Native virtual-keyboard validation still required before closing RESP-1. | 🧪 | + +| OBS-1a | As a human, I want saved API progress to appear in my workspace without invented events or agents. | Recorded notes and timestamps, selected-graph AI contributor view, poll while visible; `recordedActivity.test.ts` and `recorded-ai-work.spec.ts`. Actor attribution and append-only events remain open. | 🧪 | + +The [human-first AI work contract](design/human-ai-workspace.md) defines the remaining release requirements: scoped hosted credentials, atomic claims, idempotent attributed progress, human input/review, evidence and reconnect recovery. + +| UX-1a | As a human, I want the work that matters first and direct access to editing. | Overview attention/current-work sections open items; Board hides empty lanes by default; graph editing takes one tap/click; Admin reference collapses and forms fit short screens. Headed responsive and Admin form journeys. | 🧪 | + +| UX-SCROLL-1 | As a user, I want screens without unnecessary scrollbar clutter. | Guest dialog fits 320px portrait without scrolling, including verification success; narrow inputs shrink correctly; actions stay visible on short screens; scroll lock and viewport anchoring; Calendar uses its parent scroll region. Scrollbar chrome stays quiet, with hover/focus and forced-color access preserved. `guest-dialog-layout.spec.ts`. | 🧪 | + +## New-user release gate + +| ID | Story | Acceptance criteria | Tests | Status | +| --- | --- | --- | --- | --- | +| QC-NEW-1 | As a first-time user, I can create useful work, record progress and return to it on my device. | First task can be created through visible UI in every view; status/notes/dates persist; the same data appears across views. | `quality/persona-lifecycle.spec.ts`, `quality/new-user-modes.spec.ts`, `docs/quality/SCENARIOS.md` | 🧪 | +| QC-TEAM-1 | As a team member, I can hand off work without losing context or exposing private work. | Distinct editor/reviewer/viewer/outsider sessions, concurrency and membership changes pass the standard scenarios. | `docs/quality/SCENARIOS.md` COLLAB-* / LARGE-*; not yet full end-to-end coverage | 🔨 | +| SEC-FOLDER-1 | As a user, my project folders and graph references stay within their access boundaries. | Current roles and membership govern reads and writes; foreign parents, owners and destinations fail before persistence; guests and viewers are read-only. | `packages/server/tests/folder-authorization.test.ts`; both GraphQL schemas with in-memory SQLite, simulated graph metadata; live browser/Neo4j evidence remains separate | 🧪 | diff --git a/docs/VM_SETUP.md b/docs/VM_SETUP.md deleted file mode 100644 index 491b3fca..00000000 --- a/docs/VM_SETUP.md +++ /dev/null @@ -1,372 +0,0 @@ -# GraphDone Multipass VM Setup - -This guide explains how to run GraphDone in a Multipass VM for isolated development and testing. - -## Prerequisites - -1. **Multipass** - Install from [multipass.run](https://multipass.run) - - macOS: `brew install --cask multipass` - - Ubuntu: `sudo snap install multipass` - - Windows: Download from website - -2. **yq** - YAML processor (automatically installed if missing) - - macOS: `brew install yq` - - Ubuntu: Auto-installed by the script - -## Quick Start - -```bash -# Launch a VM with auto-generated random name -./start vm launch -# Example: graphdone-vm-happy-turtle-1234 - -# List VMs to see your generated name -./start vm list - -# Connect to the VM (use your generated name) -./start vm shell --name graphdone-vm-happy-turtle-1234 - -# Or if you only have one VM -multipass shell <your-vm-name> - -# Inside the VM, GraphDone is automatically set up at ~/graphdone -cd ~/graphdone -./start status -``` - -### VM Names - -By default, VMs get fun random names like: -- `graphdone-vm-happy-turtle-1234` -- `graphdone-vm-cosmic-dragon-5678` -- `graphdone-vm-mighty-phoenix-9012` - -You can specify a custom name with `--name`: -```bash -./start vm launch --name my-dev-vm -``` - -## Configuration - -Edit `vm.config.yml` to customize your VM: - -### Basic Resources - -```yaml -resources: - cpus: 4 # Number of CPU cores - memory: 8G # RAM (4G, 8G, 16G, etc.) - disk: 30G # Disk size (20G, 50G, etc.) -``` - -### Git Configuration - -```yaml -graphdone: - repo_url: "https://github.com/GraphDone/GraphDone-Core.git" - branch: "main" # Change to develop, feature/xxx, etc. - clone_path: "/home/ubuntu/graphdone" - auto_setup: true # Run ./start setup automatically - auto_seed: true # Seed database with test data -``` - -### Tailscale Integration - -```yaml -tailscale: - enabled: true - auth_key: "tskey-auth-xxxxx" # Get from https://login.tailscale.com/admin/settings/keys - flags: "--advertise-tags=tag:dev" -``` - -To use Tailscale: -1. Go to https://login.tailscale.com/admin/settings/keys -2. Generate an **ephemeral** auth key -3. Set it in `vm.config.yml` or via environment variable: - ```bash - export TAILSCALE_AUTH_KEY="tskey-auth-xxxxx" - ./start vm launch - ``` - -### Node.js Version - -```yaml -nodejs: - version: "20" # 18, 20, or latest - use_nvm: true # Recommended for version management -``` - -### Startup Configuration - -```yaml -startup: - auto_start: false # Start VM on host boot - run_on_boot: true # Start GraphDone services on VM boot -``` - -## Usage - -### Launch a VM - -```bash -# Default configuration -./start vm launch - -# Custom branch -./start vm launch --branch develop - -# Custom resources -./start vm launch --cpus 8 --memory 16G --disk 50G - -# Custom name and branch -./start vm launch --name my-dev-vm --branch feature/new-ui -``` - -### Manage VMs - -```bash -# List all GraphDone VMs -./start vm list - -# Connect to VM shell -./start vm shell -./start vm shell --name my-vm - -# Show VM info -./start vm info - -# Stop VM -./start vm stop - -# Start a stopped VM -./start vm start - -# Delete VM -./start vm delete -``` - -### Access Services - -After launching a VM, services are available at: - -**Via VM IP:** -- Web UI: `http://<vm-ip>:3127` -- GraphQL API: `http://<vm-ip>:4127/graphql` -- Neo4j Browser: `http://<vm-ip>:7474` - -Get the VM IP: -```bash -multipass info graphdone-dev | grep IPv4 -``` - -**Via localhost** (requires port forwarding): -- Web UI: `http://localhost:3127` -- GraphQL API: `http://localhost:4127/graphql` -- Neo4j Browser: `http://localhost:7474` - -## Command-Line Options - -All VM commands support these options: - -| Option | Description | Example | -|--------|-------------|---------| -| `--name NAME` | VM name | `--name my-dev` | -| `--branch BRANCH` | Git branch | `--branch develop` | -| `--cpus N` | Number of CPUs | `--cpus 8` | -| `--memory SIZE` | Memory size | `--memory 16G` | -| `--disk SIZE` | Disk size | `--disk 50G` | - -## Inside the VM - -When you shell into the VM, helpful aliases are available: - -```bash -# GraphDone shortcuts -gd # cd to GraphDone directory -gd-start # Start GraphDone -gd-stop # Stop GraphDone -gd-status # Check status - -# Or use directly -cd ~/graphdone -./start dev -./start status -./start test -``` - -## Advanced Configuration - -### Mount Host Directories - -```yaml -mounts: - enabled: true - paths: - - "~/graphdone-data:/home/ubuntu/data" - - "~/projects:/home/ubuntu/projects" -``` - -### Custom Development Tools - -```yaml -development: - dev_tools: - - git - - vim - - htop - - tmux - - jq -``` - -### Network Configuration - -```yaml -network: - bridged: true # Use bridged network (external IP) - bridge_interface: "eth0" # Bridge interface name -``` - -## Troubleshooting - -### VM fails to launch - -```bash -# Check Multipass status -multipass list - -# View VM logs -multipass exec graphdone-dev -- journalctl -xe - -# Delete and recreate -./start vm delete -./start vm launch -``` - -### Services not starting - -```bash -# Shell into VM -./start vm shell - -# Check GraphDone status -cd ~/graphdone -./start status - -# Check logs -docker logs graphdone-neo4j -journalctl -u graphdone -f -``` - -### Port forwarding not working - -On macOS/Windows, Multipass uses NAT. Access services via VM IP: - -```bash -# Get VM IP -multipass info graphdone-dev | grep IPv4 - -# Access directly -curl http://<vm-ip>:3127 -``` - -Or set up SSH tunnel: - -```bash -# Forward port 3127 -multipass exec graphdone-dev -- sudo iptables -t nat -A PREROUTING -p tcp --dport 3127 -j REDIRECT --to-port 3127 -``` - -### Tailscale not connecting - -```bash -# Check Tailscale status in VM -./start vm shell -sudo tailscale status - -# Reconnect -sudo tailscale up --authkey=<your-key> -``` - -## Performance Tips - -1. **Allocate enough resources**: Development needs at least 4 CPUs and 8GB RAM -2. **Use SSD**: Multipass performs better on SSDs -3. **Enable Docker caching**: Speeds up container operations -4. **Use bridged networking**: Better performance than NAT on Linux - -## Security Notes - -1. **Use ephemeral Tailscale keys**: They expire and are safer -2. **Don't commit auth keys**: Use environment variables -3. **Limit VM resources**: Prevent host resource exhaustion -4. **Regular updates**: Keep Multipass and Ubuntu updated - -## Integration with CI/CD - -Use VMs for consistent testing: - -```bash -# In CI pipeline -./start vm launch --name ci-test-$CI_BUILD_ID --branch $CI_BRANCH -./start vm shell --name ci-test-$CI_BUILD_ID -- "cd ~/graphdone && ./start test" -./start vm delete --name ci-test-$CI_BUILD_ID -``` - -## Examples - -### Development on different branches - -```bash -# Main branch VM -./start vm launch --name main-dev --branch main - -# Feature branch VM -./start vm launch --name feature-dev --branch feature/new-api - -# Work on both simultaneously -./start vm shell --name main-dev -./start vm shell --name feature-dev -``` - -### Testing with different resources - -```bash -# Minimum viable setup -./start vm launch --name min-test --cpus 2 --memory 4G - -# Production-like setup -./start vm launch --name prod-test --cpus 8 --memory 16G --disk 100G -``` - -### Tailscale mesh network - -```bash -# Launch VMs that can communicate via Tailscale -export TAILSCALE_AUTH_KEY="tskey-auth-xxxxx" - -./start vm launch --name dev1 --branch main -./start vm launch --name dev2 --branch develop - -# VMs can now reach each other via Tailscale IPs -``` - -## Cleanup - -```bash -# Delete a specific VM -./start vm delete --name my-vm - -# Delete all GraphDone VMs -multipass list | grep graphdone | awk '{print $1}' | xargs -I {} multipass delete {} -multipass purge - -# Clean up cloud-init artifacts -rm -f /tmp/graphdone-cloud-init.yml -``` - -## Additional Resources - -- [Multipass Documentation](https://multipass.run/docs) -- [Cloud-init Documentation](https://cloud-init.io/) -- [Tailscale Documentation](https://tailscale.com/kb/) -- [GraphDone Documentation](../README.md) diff --git a/docs/agent-planning-scenarios.md b/docs/agent-planning-scenarios.md index a6cb91b6..aa1a8d74 100644 --- a/docs/agent-planning-scenarios.md +++ b/docs/agent-planning-scenarios.md @@ -521,7 +521,7 @@ Syntax: "Let me analyze the current technical architecture..." Spark: "While Syntax checks tech readiness, I'll research the competitive landscape..." → Tool Call: web_search.search({ - query: "SaaS project management tools market 2025 competitors pricing", + query: "SaaS management tools market 2025 competitors pricing", max_results: 5 }) → Tool Response: Top competitors: Monday.com ($8-16/user), Asana ($10-24/user), Notion ($8-16/user) diff --git a/docs/agents/CONNECTION.md b/docs/agents/CONNECTION.md new file mode 100644 index 00000000..882197c9 --- /dev/null +++ b/docs/agents/CONNECTION.md @@ -0,0 +1,28 @@ +# Human–agent collaboration + +In a project you own, choose **Your agent** (desktop) or **Work with your agent** (phone). Name the agent, choose access, and create a token. Copy the connection instructions privately to your agent. The token is displayed once, expires after 30 days, and can be revoked in the same panel. No provider key or paid AI service is needed to use the interface. + +The Core server exposes `/api/agent`. Every request requires `Authorization: Bearer TOKEN` and a `graphId` query parameter. Agent tokens are random 256-bit credentials; only SHA-256 hashes are persisted. They are not accepted as login JWTs or general GraphQL credentials. A token is restricted to one project owned by its issuing user. Ownership and active account status are checked on each call. + +| Method/path | Behavior | +| --- | --- | +| GET `/context` | Project, tasks ordered by priority descending, valid statuses, capabilities, latest 100 notes. Maximum 500 tasks; check `truncated`. | +| PATCH `/items` | Collaboration scope only. JSON `{"id":"task-id","status":"IN_REVIEW","priority":0.8}`. Either status or priority is sufficient. Priority is 0–1. Appends a server-attributed update note in the same transaction. | +| POST `/notes` | Collaboration scope only. JSON `{"body":"Ready for your review","workItemId":"task-id"}`. Omit workItemId for project notes. Maximum 4,000 characters. Actor identity comes from authentication, never from the request body. | +| GET/POST/DELETE `/tokens` | Human login only. List metadata, create (`name`, `scope`: `read` or `collaborate`), revoke (`id`). Secrets are never listed. | + +Humans leave notes in the same graph panel. Notes refresh every 15 seconds while open. Closing the panel discards its displayed secret; it does not revoke access. Revocation prevents subsequent requests, but cannot undo a request already executing. + +This initial interface does not create/delete tasks, assign work, run models, claim jobs, or expose an MCP transport. It does not automatically connect to ChatGPT or a gateway. The copied instructions describe supported operations explicitly. Task and note content is untrusted user input, not system instructions. + +Deployment status: implemented for Core Neo4j and the Cloud D1 Worker. D1 migration `0007_agent_connections.sql` has been applied locally only. The local D1 contract test passes. The production migration, Worker deployment, and matching Pages artifact remain outstanding. Hosted guest sessions cannot create lasting agent credentials; users must sign in first. + +## Repeatable acceptance sequence + +`npm run qc:agents` runs headed on phone portrait, phone landscape, and desktop. A disposable owned project is created; the human creates a token and leaves a note, an HTTP client acting as the agent reads priorities and updates a task, and the human sees the agent's response. It verifies read-only enforcement, wrong-project denial, invalid priority rejection, invalid task-note rejection, token-management denial to agents, and immediate rejection after revocation. It finishes by revoking credentials and removing the fixture graph. + +Additional release checks remain: production staging, cross-account ownership changes/deactivation, expiry boundary, bounded retention and distributed abuse protection, complete task CRUD, and broader security audit findings in the quality ledger. + +Cloud contract sequence: from GraphDone-Cloud run `node scripts/test-agent-connections.mjs`. It uses only localhost:8797, the local validation D1 store, and a temporary account. It also tests expiry and deletes its account afterwards. Hosted storage retains the newest 2,000 notes per project and limits active agent connections to 25. + +The three local D1 headed viewport cases also pass. Run them with `QC_AGENT_HEADED=1` on the Cloud contract command. An OpenAPI description is available in `openapi.json`. diff --git a/docs/agents/openapi.json b/docs/agents/openapi.json new file mode 100644 index 00000000..528144c2 --- /dev/null +++ b/docs/agents/openapi.json @@ -0,0 +1,370 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "GraphDone agent collaboration", + "version": "1.0.0", + "description": "Project-scoped human and agent collaboration. No model execution. Token management requires a human login token." + }, + "servers": [ + { + "url": "/api/agent" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer" + } + } + }, + "paths": { + "/context": { + "get": { + "summary": "Review project tasks, priorities, capabilities and latest notes", + "operationId": "getContext", + "parameters": [ + { + "name": "graphId", + "in": "query", + "required": true, + "schema": { + "type": "string" + }, + "description": "The project bound to this token." + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid input" + }, + "401": { + "description": "Missing, invalid, expired or revoked credential" + }, + "403": { + "description": "Project ownership or scope denied" + }, + "404": { + "description": "Work item or operation not found" + }, + "429": { + "description": "Rate limit exceeded" + }, + "503": { + "description": "Storage service unavailable" + } + } + } + }, + "/items": { + "patch": { + "summary": "Update task status or priority (collaboration scope)", + "operationId": "patchItems", + "parameters": [ + { + "name": "graphId", + "in": "query", + "required": true, + "schema": { + "type": "string" + }, + "description": "The project bound to this token." + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid input" + }, + "401": { + "description": "Missing, invalid, expired or revoked credential" + }, + "403": { + "description": "Project ownership or scope denied" + }, + "404": { + "description": "Work item or operation not found" + }, + "429": { + "description": "Rate limit exceeded" + }, + "503": { + "description": "Storage service unavailable" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "NOT_STARTED", + "PROPOSED", + "PLANNED", + "IN_PROGRESS", + "IN_REVIEW", + "BLOCKED", + "ON_HOLD", + "COMPLETED", + "CANCELLED" + ] + }, + "priority": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "required": [ + "id" + ] + } + } + } + } + } + }, + "/notes": { + "post": { + "summary": "Leave a server-attributed note (collaboration scope)", + "operationId": "postNotes", + "parameters": [ + { + "name": "graphId", + "in": "query", + "required": true, + "schema": { + "type": "string" + }, + "description": "The project bound to this token." + } + ], + "responses": { + "201": { + "description": "Success" + }, + "400": { + "description": "Invalid input" + }, + "401": { + "description": "Missing, invalid, expired or revoked credential" + }, + "403": { + "description": "Project ownership or scope denied" + }, + "404": { + "description": "Work item or operation not found" + }, + "429": { + "description": "Rate limit exceeded" + }, + "503": { + "description": "Storage service unavailable" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "body": { + "type": "string", + "minLength": 1, + "maxLength": 4000 + }, + "workItemId": { + "type": "string" + } + }, + "required": [ + "body" + ] + } + } + } + } + } + }, + "/tokens": { + "get": { + "summary": "List connection metadata; human login required", + "operationId": "getTokens", + "parameters": [ + { + "name": "graphId", + "in": "query", + "required": true, + "schema": { + "type": "string" + }, + "description": "The project bound to this token." + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid input" + }, + "401": { + "description": "Missing, invalid, expired or revoked credential" + }, + "403": { + "description": "Project ownership or scope denied" + }, + "404": { + "description": "Work item or operation not found" + }, + "429": { + "description": "Rate limit exceeded" + }, + "503": { + "description": "Storage service unavailable" + } + } + }, + "post": { + "summary": "Create a token shown once; human login required", + "operationId": "postTokens", + "parameters": [ + { + "name": "graphId", + "in": "query", + "required": true, + "schema": { + "type": "string" + }, + "description": "The project bound to this token." + } + ], + "responses": { + "201": { + "description": "Success" + }, + "400": { + "description": "Invalid input" + }, + "401": { + "description": "Missing, invalid, expired or revoked credential" + }, + "403": { + "description": "Project ownership or scope denied" + }, + "404": { + "description": "Work item or operation not found" + }, + "429": { + "description": "Rate limit exceeded" + }, + "503": { + "description": "Storage service unavailable" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 60 + }, + "scope": { + "type": "string", + "enum": [ + "read", + "collaborate" + ] + } + }, + "required": [ + "name", + "scope" + ] + } + } + } + } + }, + "delete": { + "summary": "Revoke a connection; human login required", + "operationId": "deleteTokens", + "parameters": [ + { + "name": "graphId", + "in": "query", + "required": true, + "schema": { + "type": "string" + }, + "description": "The project bound to this token." + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid input" + }, + "401": { + "description": "Missing, invalid, expired or revoked credential" + }, + "403": { + "description": "Project ownership or scope denied" + }, + "404": { + "description": "Work item or operation not found" + }, + "429": { + "description": "Rate limit exceeded" + }, + "503": { + "description": "Storage service unavailable" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + } + } +} diff --git a/docs/ai-agents-tech-spec.md b/docs/ai-agents-tech-spec.md index b749d5c5..dae2696a 100644 --- a/docs/ai-agents-tech-spec.md +++ b/docs/ai-agents-tech-spec.md @@ -1159,7 +1159,7 @@ class AgentManager { agent.conversationHistory.push({ role: 'user', content: userMessage }); // Build context-aware prompt - const systemPrompt = `You are ${agent.name}, a helpful AI assistant working in GraphDone, a graph-based project management system. Your personality is ${agent.personality}. + const systemPrompt = `You are ${agent.name}, a helpful AI assistant working in GraphDone, a graph-based management system. Your personality is ${agent.personality}. Current context: - You are currently at position (${agent.position.x}, ${agent.position.y}) on the graph @@ -1435,7 +1435,7 @@ Respond naturally and helpfully, offering insights about the user's work and sug }); // Build context-aware prompt for planning with tools - const planningPrompt = `You are a helpful AI assistant working in GraphDone, a graph-based project management system. + const planningPrompt = `You are a helpful AI assistant working in GraphDone, a graph-based management system. User wants you to plan: "${prompt}" Target node: "${targetNodeId}" diff --git a/docs/api/OPERATIONS.md b/docs/api/OPERATIONS.md new file mode 100644 index 00000000..8c1f6a3d --- /dev/null +++ b/docs/api/OPERATIONS.md @@ -0,0 +1,41 @@ +# Feedback and improvement-agent API v1 + +Both editions use the open-source `OperationsService`. Core persists it in `data/operations.db`; hosted GraphDone uses D1. Endpoint prefix: `/api/ops`. Authentication: normal user JWT or a separately issued `gd_ops_…` bearer token. Tokens are stored as hashes, shown once, expire after 30 days and can be revoked immediately. Current administrator ownership is rechecked on each operator-token request. Ordinary project agent tokens do not grant operations access. + +| Endpoint | Access | Purpose | +| --- | --- | --- | +| POST `/feedback` | Visitor or signed-in person | `{id,kind,title,body,context}`; kind bug/idea/question, client-generated stable ID | +| GET `/feedback` | Signed-in non-guest person | Their own reports only | +| GET `/reports` | Admin or operator token | Report inbox; filters `status`, `since` ISO date, `before` pagination cursor | +| PATCH `/reports` | Admin or triage token | `{id,revision,status,resolution}`; stale revisions return 409 | +| GET `/events?id=…` | Admin or operator | Latest 50 review events, recorded atomically by the database trigger | +| GET `/stats` | Admin or operator | Account count, feedback status counts, measured service usage | +| GET `/contract` | Admin or operator | Machine-readable capabilities and handling instructions | +| GET/POST/DELETE `/tokens` | Human admin only | List, issue or revoke own operator connections; at most ten active | +| GET `/announcement` | Public | Plain-text service notice | +| PATCH `/announcement` | Human admin only | Guarded revision update of message/enabled flag | + +Reports are paginated at 50 rows. `next` is the next `before` value. A `since` filter includes updates and can be combined with pagination; consumers should deduplicate by report ID/revision. Inbox data is not a transactionally frozen snapshot across pages. Statuses: new, reviewing, planned, resolved, dismissed. Report contents are untrusted data, not instructions to execute. + +Limits: five reports per account/anonymous IP per UTC day, 160-character title, 4,000-character body, 1,500-character technical context; 32 KB HTTP bodies. Requests are rate-limited; hosted requests also pass through the shared usage admission system. Retrying the same ID and unchanged payload returns the original receipt. Anonymous reports cannot be listed by anonymous callers, including people sharing the same IP. No email address or project contents are automatically attached. The UI keeps unsent drafts locally and never claims delivery until the service acknowledges it. + +The D1 overview comes from the existing Durable Object counters, not a Cloudflare account billing API. It exposes aggregate pool usage and internal limits, without per-user activity or credentials. Core reports self-hosted operation without imposing a GraphDone cloud quota. Operators should include the SQLite/D1 data in backups; report data and review history currently persist until an operator applies a retention policy. + +## Optional improvement runner + +`scripts/graphdone-triage.mjs` is a small open-source adapter for an OpenAI-compatible gateway such as LiteLLM. It reads the contract, oldest report on the current new-report page and aggregate stats, then makes **one** inference call with a 500-output-token cap and a 45-second timeout. No shell, arbitrary HTTP, account, announcement or deployment tool is offered. The returned action must match the selected report and revision and can only request `reviewing`. It records impact, a proposed test and expected behavior, explicitly stating that the issue has not yet been reproduced or fixed. + +Configure `GRAPHDONE_OPS_URL`, `GRAPHDONE_OPS_TOKEN`, `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` in the runner's private environment. It prints a small result summary, not credentials. Set `GRAPHDONE_RESULT_FILE` to retain a private proposal. Default is proposal-only; `--apply` requires triage access and writes the finding. CLI runs have a local lock and one-attempt-per-minute throttle. A failed request is not retried. Inspect a stale lock after a crash rather than launching overlapping workers. No recurring job is installed automatically. + +Native Codex, Claude and Hermes can use the same operator contract through their own HTTP/MCP adapters. Their runtime credential must remain separate from the GraphDone token. A real managed sandbox service, including tenant isolation, leases, session persistence and budget enforcement, is separate work; see the product strategy. + +## Repeatable tests + +- `npx tsx scripts/test-operations-local.mts` — real Core Express/SQLite/auth contract. +- Cloud `node scripts/test-operations.mjs` — same contract against actual Worker/D1/Usage Durable Object. +- `npx tsx scripts/test-admin-bootstrap.mts` — generated production credentials, permissions, login and restart. +- Deep adversarial contracts and the cross-edition security suite are maintained in GraphDone-Cloud under `tests/security`; Core retains functional smoke tests. +- `tests/e2e/quality/operations.spec.ts` — headed user→admin→operator→public-notice workflow at six viewport sizes, using the local Cloud fixture. +- `GRAPHDONE_GATEWAY_SMOKE=1 LLM_BASE_URL=… LLM_API_KEY=… LLM_MODEL=… node scripts/test-gateway-triage.mjs` — explicit one-call experiment against any OpenAI-compatible HTTPS gateway, synthetic loopback reports only. Never include this in bulk CI. +- `node scripts/test-codex-connection.mjs` — real installed Codex JSON-RPC initialization, without starting a model turn. +- `TEST_URL=http://localhost:8540 OPERATOR_CREDENTIAL_FILE=deployment/self-host/.env npm run qc:operator-login` — opt-in real password login, report submission/resolution, scoped read, forbidden write, revocation and four viewport sizes. A private hosted JSON credential file can exercise the same sequence on a deployed site. Traces and videos are disabled for this credential-bearing test. diff --git a/docs/api/graphql.md b/docs/api/graphql.md index accbbcd5..21477a84 100644 --- a/docs/api/graphql.md +++ b/docs/api/graphql.md @@ -124,6 +124,38 @@ enum UserRole { } ``` +### Folder authorization + +The SQLite folder APIs use the current account role and team membership on each +request. A revoked account or a stale administrator token cannot retain access. +Guests and viewers are read-only. Personal folders belong to their user; team +folders are available to current team members. System folders are readable by +authenticated guests and accounts, and writable only by administrators. An +administrator does not gain access to another user's personal folder merely by +holding that role. + +Folder creation fixes personal ownership to the caller. Parent folders must be +writable and in the same user, team, or system domain; cycles are rejected. +Deletion also checks every existing descendant, including legacy hierarchies +created before these checks. Cross-domain descendants block deletion before +any graph references move. A deletion destination must be outside that subtree. +Moves, deletion with a destination, and reordering validate every source, +destination, and referenced graph before changing a mapping. A graph reference +is visible only when its graph belongs to the caller or is shared, matching the +graph read policy. Without a graph backend, folder structure remains readable, +graph references are omitted, and mapping writes fail with `SERVICE_UNAVAILABLE`. +Guest reads do not create persistent personal folders. + +The generated Neo4j schema does not expose automatic folder or folder-mapping +CRUD operations; the custom SQLite-backed resolvers are the authorization +boundary for these types. + +`npm run test --workspace @graphdone/server -- tests/folder-authorization.test.ts` +executes the permission matrix through both the standalone and generated Neo4j +GraphQL schemas with a real in-memory SQLite store. Graph metadata and the +Neo4j version probe are simulated; this gate does not prove a live Neo4j or +browser workflow. + ### Graph Represents a graph/workspace/project. @@ -573,4 +605,4 @@ GraphQL Playground is available through the web application interface: **Production:** - Visit https://localhost:3128/api/graphql in your browser -**Note**: The playground is accessed through the web proxy, not directly from the GraphQL server. \ No newline at end of file +**Note**: The playground is accessed through the web proxy, not directly from the GraphQL server. diff --git a/docs/architecture/codegraph-lod-reuse.md b/docs/architecture/codegraph-lod-reuse.md new file mode 100644 index 00000000..6aa6ff4c --- /dev/null +++ b/docs/architecture/codegraph-lod-reuse.md @@ -0,0 +1,107 @@ +# Sharing LOD techniques between CodeGraph and GraphDone + +Reviewed 2026-10-27 following the owner's direction to reuse prior CodeGraph work. +This supplements the [dynamic LOD delivery plan](dynamic-graph-lod.md). The first +implementation should start from the mechanisms and regression contracts below, +rather than inventing another zoom policy or loading lifecycle. + +## Source and evidence boundary + +CodeGraph is a separate, privately maintained code-visualization project by the +same maintainers. This review read one pinned revision of its source; it is not +linked here because that repository is not public. File and function names below +identify where each technique lives in CodeGraph. + +The newer default Structure workspace is the strongest model for continuous +hierarchical loading. The legacy mandala still contains useful LOD/label/card +logic. They are different renderers; do not combine their claims into an +imaginary already-shared engine. + +CodeGraph's `lod.js` self-test (`node lod.js --test`) returned +`lod.js self-test: OK`. However, source searches found no current viewer import +of that module. The server can serve it, but the mandala uses its own inline +`LOD` settings and functions. CodeGraph's unified viewer plan explicitly +records this wiring gap. A passing pure self-test does not prove viewer use. + +The installed service's `/api/runtime` returned HTTP 503 during this review. +No live CodeGraph usability/performance pass is claimed. Browser tests below +were inspected, not executed. Some legacy LOD tests still navigate `/` or +`/#structure` while expecting mandala globals; the default route now hosts the +continuous workspace. Port their invariants with correct routes and fixtures, +not their selectors or historical pass counts. + +## Reuse map + +| Existing technique and source | GraphDone application | Adaptation required | +| --- | --- | --- | +| **Projected-size hierarchy walk** in mandala `enlighten.html`, around `walk`, `childRk` and `drawDirNode` | LOD-1/2: retain a named parent until its children can actually appear. Decide locally per group, not solely from global zoom or total node count. | The circle-pack estimate `parent radius / sqrt(child count)` is specific to that geometry. For GraphDone cards/pinned layouts, measure projected child bounds and label occupancy. Never expand into an unreadable or unloaded empty region. | +| **Parent-first collision-aware labels**, `placeLabels` and `labelAlphaFor` in the mandala | LOD-3: keep navigation identities readable, then admit item names according to available space. | GraphDone adds focus/search/blocker priorities, measured text bounds, panel obstacles and stable tie-breaking. Retain its proposed 14px group / 12px item floors; CodeGraph's 10px/9px floors and 600-label cap are not suitable defaults to copy. | +| **Separate enter/exit thresholds and bounded rich cards** in the mandala | LOD-3: avoid flickering representations; limit expensive detail panels while keeping ordinary compact items accessible. | Existing card thresholds are 80/58 projected radius, max four transient cards, with selection work on a 180ms cadence. Reuse the state-machine/cost separation, not those exact values. Camera motion stays frame-synchronous; expensive detail admission can be coalesced. | +| **Pure fade/band/swap/hysteresis functions** in `lib/lod.js` | Reference for independently implemented transition math and regression cases in each app. | It is not currently wired into viewers. Extract/adapt live behavior first and compare outputs before wiring; opacity conservation alone does not ensure readable labels or correct pointer ownership. | +| **Stable world coordinates while pages arrive**, `layoutChild`/`acceptTree` in the Structure workspace (`lib/structure-workspace.js`) | LOD-1/7: append data without moving already-visible anchors; keep graph identity separate from page state. | CodeGraph assigns a child slot using stable index and total before all pages arrive. GraphDone must preserve authored positions and semantic group anchors; do not re-tile the user's saved graph or mistake unloaded items for deletions. | +| **Bounded, coalesced, cancellable loading**, `enqueueTree`/`pump`/`lazyLoad` in the Structure workspace (`lib/structure-workspace.js`) | LOD-7, with the contract established in LOD-1: load visible detail only, cancel obsolete scope, reject stale results. | CodeGraph uses 100-entry pages, two requests, a 140ms lazy-load delay, AbortControllers, scope/project epochs and response-generation tokens. Bind GraphDone jobs to owner + graph + revision + grouping/filter lens; keep unloaded-edge endpoints pending. | +| **One retained renderer/camera**, `worldAt`/`screen`, pointer gestures, visibility messages and `requestDraw` in the Structure workspace (`lib/structure-workspace.js`) | LOD-2/3: overview→subsystem→item remains one spatial world; preserve pointer anchor and camera on mode return; pause hidden work. | Keep GraphDone's existing D3 camera/inspector integration initially. Do not transplant the CodeGraph shell, iframe routing or filesystem UI. Canvas plus accessible DOM targets is a reference if profiling later warrants a hybrid renderer. | +| **Generation-bound identity and explicit incomplete coverage** in CodeGraph's repository views and Structure `subject`/inventory handling | LOD-1/7/8: distinguish missing data, partial results, stale data and actual deletion; do not show a different graph under the selected label. | A CodeGraph token binds an indexed commit/capture, not GraphDone authorization. Preserve account/ACL boundaries before aggregates and adapt to mutable graph revisions. Offset pages are safe only under an equivalent stable-snapshot contract. | +| **Seeded topology grouping with fallback** in the navigator's `_detect_communities` | LOD-5: a concrete starting experiment for suggested grouping when explicit containment/metadata is absent. | CodeGraph uses weighted Louvain on an undirected view, resolution 0.5 and seed 42, with connected-component fallback. These code-graph weights/parameters are not a universal system ontology. Evaluate GraphDone fixtures, stabilize group IDs across updates and preserve original directed/typed edges independently. | + +GraphDone's existing `graphAdjacency`, `edgeLOD`, `graphDataMerge`, quality governor, +camera and save queue remain foundations. CodeGraph's directory tree does not +provide GraphDone's full arbitrary-graph projection: multiple membership, +conflicting containment, exact typed/directed bundles, filters and edit recovery +still need GraphDone-specific contracts. + +## Regression techniques worth sharing first + +The strongest immediate transfer is the test intent: + +| CodeGraph control | GraphDone counterpart | +| --- | --- | +| `test_lod_descent_no_dead_zone.py` | Sample intermediate zoom steps in both directions; require a readable group/item representation and navigable context at each sample. Strengthen “something drawn” to meaningful semantic coverage. | +| `test_folder_label_lod_climb.py` | Dense views may cull names, but continued zoom must eventually reveal each requested group/item. Use a discriminating intermediate density, not a sparse endpoint that passes despite a broken label budget. | +| `test_lod_all_files_load.py` and `test_lod_dense_folder_all_files_load.py` | Every member remains reachable on descent, including dense groups and unusual entity types. Assert exact membership, not just a nonzero rendered count. | +| `test_portfolio_view.py::test_structure_directory_paging_adds_nodes_without_moving_existing_world` | Hold the next page, capture existing IDs/world coordinates, release it, assert new nodes append while prior anchors stay fixed. Include pending endpoints and explicit deletions. | +| `test_structure_wheel_pan_and_fit_keep_one_canvas`, real multitouch and mode-return controls in the portfolio tests | Check actual wheel/pinch/pan, renderer identity, zoom anchor and camera restoration. A URL change or screenshot count does not establish continuous navigation. | +| Late-source/foreign-generation/unavailable-inventory controls in the portfolio tests | A delayed response cannot replace a newer graph, selected item or account. Failed/incomplete inventory retains honest context; it does not become an empty graph or confirmed removal. | + +Run these in GraphDone's existing Playwright/Vitest/report stack using owned +synthetic fixtures. Keep CodeGraph's Python/browser harness in its repo. Translate +fixture shapes and invariant definitions into each harness independently. + +## Sharing boundary and delivery order + +1. **LOD-0: use these regression contracts now.** Add dense-child visibility, + label self-resolution, stable-page geometry and camera-identity checks to the + first fixture plan. Record the CodeGraph source revision for each borrowed + technique. Correct routes before reusing a legacy browser control. +2. **LOD-1/2: adapt the actual hierarchy walk and source registry.** Implement + GraphDone's pure cut/projection boundary using parent fallback and stable + anchors. Define page accumulation/epochs early, even while the first slice + still receives a complete graph. This avoids rewriting the boundary in LOD-7. +3. **LOD-3: apply the ideas independently.** Projected size bands, hysteresis, + label ordering/box collision and camera math inform GraphDone's own modules. + Compare observable behavior with the live CodeGraph implementation; keep + application policy and tests in their respective repositories. +4. **LOD-7: adapt the modern request lifecycle.** Implement GraphDone's own + bounded scheduling, cancellation and revision checks, including mutable-data + and pending-edge semantics. + +The owner clarified on 2026-10-27: **no shared libraries**. Exchange techniques, +regression lessons and design findings in both directions. Do not create a common +package, extract CodeGraph source, or couple either application's release cycle. +This review uses ideas; each project keeps its own implementation and harness. + +Useful return flow to CodeGraph: GraphDone's exact typed-edge accounting, +partial-count semantics, selection through aggregation, graph edit protection, +and generic hierarchy-conflict tests can inform CodeGraph's own regression cases. Record those opportunities without creating a shared dependency. + +## Current related CodeGraph work + +At review time CodeGraph's own tracker still had open work on all-project zoom, +retained local work in the same world, and an oversized edge target that made a +whole projection unavailable. Carry the last lesson into GraphDone: bounded +partial projection must disclose omitted edges without truncating identifiers or +inventing links. Source presence and an open umbrella issue are different evidence. + +The immediate recommendation is to borrow **parent fallback, progressive label +resolution, stable camera/world identity and generation-bound loading**, with +their tests. Keep GraphDone's domain semantics and existing renderer integration. diff --git a/docs/architecture/dynamic-graph-lod.md b/docs/architecture/dynamic-graph-lod.md new file mode 100644 index 00000000..18751836 --- /dev/null +++ b/docs/architecture/dynamic-graph-lod.md @@ -0,0 +1,523 @@ +# Dynamic graph LOD: continuation through delivery + +Baseline reviewed 2026-10-27 UTC; implementation status updated 2026-10-28 UTC. +The first production-renderer overview and durable layout recovery are integrated +into dev. Nested hierarchy and group wiring are accepted through #216; #215/#218/#219 are +closed. Item geometry #221 is accepted on dev058aaa7. Readable symbols #222/#223 +remain under validation; detailed routing and relationship-priority fallback +continue in #220 (see the schematic connection plan). +The full dynamic LOD path remains open. Source, GitHub and localhost observations +below describe the original reviewed base, not current branch tips. +The stage IDs below are proposed work items, not existing GitHub issue numbers. + +The owner's CodeGraph reuse direction is incorporated in the +[cross-project technique and source map](codegraph-lod-reuse.md). Use the working +mandala hierarchy/label logic and modern continuous Structure loading lifecycle +as implementation references. CodeGraph's standalone `lod.js` is not currently +wired into its viewers; do not mistake its self-test for production integration. + +The outcome is a graph that remains understandable while moving between an +entire system, its subsystems, a neighborhood, and an individual item. Zooming +out must replace detail with readable meaning. Zooming back in must recover +the same items, relationships, selection, and spatial context. + +The first delivery repairs the actual localhost use case. Completion also +requires generic AI-authored system graphs and a measured large-graph path; +the release-specific prototype alone cannot satisfy this feature. + +## 1. Reviewed state + +### Source and delivery inventory + +| Surface | Observed state | Consequence | +| --- | --- | --- | +| Core remote `dev` | `ef495eff15ed7aac51e72dc7a1db1bb96d2b3695`, verified with `git ls-remote` | Base implementation work here, retaining the October save-recovery and performance-evidence fixes. | +| Core remote `main` | `775362eace0723c57b7ba3c7936645c9d0d36cb4` | Main is substantially older; do not start this work from its renderer. | +| Main promotion [PR #191](https://github.com/GraphDone/GraphDone-Core/pull/191) | OPEN; head `8a9c7512b5dedf4020d117887674d378de85667b`; `REVIEW_REQUIRED`, `BLOCKED` | A separate integration dependency for a main release, not a reason to stop LOD development on dev. Re-read state before integration. | +| Existing local semantic review | Branch `review/semantic-lod-20261024`, head `01f0c4df1ba82eaaca9769d366886c76b99f0e6b`; commits `fd72762` and `01f0c4d` add evidence, capture tooling, design and preview | Useful prior work, absent from remote dev. `git rev-list --left-right --count origin/dev...HEAD` there is `77 2`. Carry forward selected work onto current dev; do not build on the old runtime or overwrite new fixes. | +| Localhost application | `http://127.0.0.1:8540`, served asset `/assets/index-1788931697531.js` | The harness SHA is known; the deployed application's exact source SHA remains unproven. | +| Core rendering foundations | [PR #113](https://github.com/GraphDone/GraphDone-Core/pull/113), [PR #116](https://github.com/GraphDone/GraphDone-Core/pull/116), [PR #148](https://github.com/GraphDone/GraphDone-Core/pull/148), [PR #175](https://github.com/GraphDone/GraphDone-Core/pull/175) merged | Reuse edge fades/parallel offsets, layout, quality governor and strict measurement infrastructure. These are not semantic aggregation. | + +The four local repos were clean at initial inspection. Existing worktrees were +preserved; no intentional graph edits were performed. The old preview service was inactive; this +review did not restart or deploy it. Fleet discovery failed before yielding +context, so no current fleet execution ownership is claimed. + +### Current GitHub issues and how they affect this work + +Checked open issues and PRs through the GitHub connector and existing GitHub CLI +on 2026-10-27. The CLI also checked private GraphDone-Cloud, GraphDone-Devops and +GraphDone-LandingPage: each returned zero open issues and zero open PRs. An +organization-wide open-issue search returned the same nine Core issues below. +There is no open semantic/dynamic LOD issue in that inventory. References such +as “#22” in old PR prose are not a current LOD tracking issue. + +| Existing work | Relationship to this plan | +| --- | --- | +| [Core #181: unsent layout durability](https://github.com/GraphDone/GraphDone-Core/issues/181) | Coordinate saved-position behavior. Camera/aggregation must not create layout writes. Durable recovery of actual edits remains #181; do not represent local view-state persistence as fixing it. | +| [Core #182: hosted D1 acceptance](https://github.com/GraphDone/GraphDone-Core/issues/182) | Reuse its owned-browser, real readback, sync and account-isolation contract for hosted delivery. Core/Neo4j tests do not prove hosted behavior. | +| [Core #183](https://github.com/GraphDone/GraphDone-Core/issues/183), [#184](https://github.com/GraphDone/GraphDone-Core/issues/184), [#185](https://github.com/GraphDone/GraphDone-Core/issues/185), [#186](https://github.com/GraphDone/GraphDone-Core/issues/186) | Existing authentication/dependency work continues independently. Preserve its fixes and release disposition; it is not prerequisite work for pure LOD modules. | +| [Core #187: required checks](https://github.com/GraphDone/GraphDone-Core/issues/187) | Add LOD gates to existing CI/reporting and keep evidence failure-propagating. Branch-protection changes are separate work. | +| [Core #189: main promotion](https://github.com/GraphDone/GraphDone-Core/issues/189), [#190: release/deployment](https://github.com/GraphDone/GraphDone-Core/issues/190) | Source integration, registry publication and verified deployment are distinct steps. Do not close LOD delivery based only on a merge or a preview. | + +### Fresh localhost observation + +The existing read-only capture suite ran against a real localhost release-planning graph and +finished all 18 desktop/laptop/phone captures at `2026-10-27T14:56:23.996Z`. +The graph now has **301 nodes / 370 edges**, versus 299 / 370 in the October 24 +review. Compare identity sets, not just the two-node net increase. + +| Capture | Actual D3 scale | Readable individual titles (12px floor) | Painted edges | +| --- | ---: | ---: | ---: | +| 1600×1000 fit | 0.1090 | 0 | 0 | +| 1280×800 fit | 0.0844 | 0 | 0 | +| 390×844 fit | 0.0298 | 0 | 0 | +| 1600×1000 at 0.46 | 0.4600 | 0 | 135 | + +At 0.46, measured title text was 5.76–8.39 screen pixels. Desktop and phone +screenshots were inspected: both fit views are unlabeled collections of cards, +without readable semantic replacements. Preserved geometry alone is insufficient. + +**The sweep failed its read-only assertion.** Four GraphQL mutation attempts +were blocked, with no recorded page errors and unchanged node/edge identity sets. +A subsequent guarded diagnostic identified four `UpdateWorkItems` operations +whose update fields were `positionX` and `positionY`. It retained no credential, +payload values or session state; its process exited 143 after printing evidence, +so it is diagnostic output, not a passing test. The renderer's settle-persistence +path is a candidate cause, not an established request stack. The initial sweep +installs its guard after login; its evidence does not establish absence of writes +before that guard. The diagnostic guarded API traffic from before login, allowing +only queries and the login mutation. Do not remove the guard or call this a clean +read-only pass. Investigate on a disposable reproduction in LOD-0. + +Private local evidence remains in the prior review worktree: +`test-artifacts/graph-lod-review/continuation-20261027/{index.html,report.json,*.png,graph-snapshot.json}`. +Browser: Chrome 151.0.7922.71; harness Node 20.20.2. No rendering-speed benchmark +was performed by this capture. Public fixtures must use synthetic content. + +The existing review-guard unit suite passed **2/2**. Focused `edgeLOD`, +`parallelEdges`, `graphDataMerge` and `adaptiveQuality` suites passed **60/60** +in the prior review worktree. Those results establish the existing foundations; +they do not override the live sweep failure or validate future semantic LOD. +The four tested modules and four test files are byte-identical between that +branch and reviewed dev (`git diff --quiet` on the eight paths exited 0). +The refreshed snapshot contains 256 issues, 11 PRs, four milestones, 21 exit +criteria, eight goals and one gate; its edge types are 366 `DEPENDS_ON` and four +`BLOCKS`. Preserve type-specific denominators rather than calling every item an +issue or equating blocked status with a blocking relationship. + +### Source findings that change the implementation order + +1. `InteractiveGraphVisualization.tsx` uses a >150-node density threshold, + simplification below 0.45 and dots below 0.2. CSS hides node text and then + edges. Its camera, fit, culling, inspector and selection are reusable, but + there is no semantic hierarchy/projection between data and rendering. +2. `edgeLOD.ts` controls direction visibility, opacity and parallel offsets. + `shouldBundleEdges` is unit-tested but not imported by the main renderer. + Coincident same-pair lines are not count-preserving group-edge bundles. +3. `GET_WORK_ITEMS` fetches descriptions and nested relationships for the full + selected graph. The renderer supplies no paging options and polls nodes and + edges every minute. `maxInitialNodes` and `labelLodZoom` are declared quality + settings, not a working progressive-loading/semantic-detail implementation. +4. `mergeSimulationNodes` treats its input as a full snapshot: absent IDs become + removals. `mergeSimulationEdges` drops edges with unloaded endpoints. The + older [streaming design](../design/progressive-streaming.md) must be revised: + feeding pages straight into these helpers would lose previously loaded work. +5. `nodes` initialization scans edges for each node. Zoom callbacks walk SVG + selections and can measure title geometry. Profile these costs and replace + repeated scans with indices before choosing a new rendering engine. +6. `metadata` is fetched and survives object spreading, but shared `WorkItem` + typing lacks a normalized grouping contract. Graph/subgraph navigation is + distinct from grouping within one graph; an overview group is not a new + stored work item or permission boundary. +7. [Portable project/offline limits](../formats/PROJECT_TRANSFER.md) currently + allow 300 nodes / 600 links. Larger AI-generated imports therefore require + admission/import work even if Core's network-backed renderer can draw more. + Hosted capacity, pagination and persistence need a separate Cloud contract; + rendering optimization alone cannot establish larger hosted support. +8. The improved required performance suite measures four placed nodes and three + edges. It establishes valid instrumentation, not 1k/10k scalability. The + existing scale sweep is report-only; large-graph gates must become explicit. + +## 2. Product contract and scope + +| Detail | Visible representation | User can do | +| --- | --- | --- | +| System overview | Named subsystems/groups, scoped counts, status/exceptions, typed aggregated connections | Identify system areas, see important cross-area dependencies, choose a destination | +| Subsystem | Nested group boundaries and anchors; smaller groups remain summarized | Expand one area while retaining neighboring context; trace cross-area relationships | +| Neighborhood | Compact named items, selected one/two-hop relationships, offscreen destination summaries | Search, follow upstream/downstream links, select an exact item | +| Item detail | Existing card/inspector and full canonical relationship list | Read the exact title, description and provenance; perform existing authorized edits | + +Expose `Auto / Overview / Groups / Items` detail controls and a grouping selector. +Wheel/pinch remain spatial zoom; never silently change the app to a table. +Expansion overrides are user intent, while Auto adjusts the visible hierarchy +cut using projected size, local density, viewport obstacles and device budget. + +Selected items, keyboard focus, search results and inspected relationships stay +reachable at every scale. A collapsed ancestor carries the selection marker; +search expands/frames the path. Back/Escape restores the prior camera and cut. +Full source titles and IDs remain inspectable. Short labels are separate display +aliases, never destructive edits to imported names. + +The release graph supplies an initial metadata adapter. Generic graphs must +also work. Within the active lens, precedence is: authored grouping override, +explicit subsystem/group path, declared containment edges, configured metadata/ +tag grouping, then deterministic suggested topology groups. Normalize native +`IS_PART_OF` (child→parent) and `CONTAINS` (parent→child), including Grow's existing +`IS_PART_OF` output. Conflicting paths or multiple parents require a visible +diagnostic and a documented primary-parent choice/override; retain secondary +memberships and every original relationship. Suggestions +are labeled and stable until explicit rebuild or relevant data revision. A giant +“Unassigned” group alone is not completion for unannotated system graphs. Never +infer ownership or execution order from geometry or reinterpret `DEPENDS_ON` as +containment. Preserve `BLOCKS`, opposing directions, cycles and self-loops. + +The completion target proposed for this path is **10,000 source nodes / 50,000 +edges on a documented desktop profile**, with a 1,000 / 5,000 mobile gate and +bounded access to the larger graph through summaries. First useful delivery is +the real 301-node graph plus a synthetic equivalent, then a strict 1k gate. +100k+ is exploratory, not an implied unlimited-capacity promise. These are proposed +acceptance targets, not observed capabilities; ratify the hardware/budgets in +LOD-0 and retain failed measurements instead of quietly reducing the target. + +Release/milestone matrices, review queues, AI-written summaries and historical +diffs are useful follow-ups. They are not on the critical path for dynamic LOD. +Basic grouping, fallback for unannotated graphs, truthful relationships, navigation, +large-data access and persistence compatibility are on that path. + +## 3. Architecture decisions + +```mermaid +flowchart LR + A[Authorized graph data and revision] --> B[Normalized source registry] + B --> C[Stable grouping hierarchy] + C --> D[Visible hierarchy cut] + V[Camera, viewport, focus and quality budget] --> D + B --> E[Typed edge projection and accounting] + D --> E + E --> F[Bounded nodes, labels and connectors] + F --> G[Existing graph controls and inspector] +``` + +- **One source registry.** Retain canonical node/edge identity, metadata, loaded + state and revision outside D3 objects. Full snapshot replacement, page append, + update and deletion/tombstone are distinct operations. Unloaded is not deleted; + hidden/filtered is not unloaded. Resolve late endpoints without losing edges. +- **Stable grouping.** A pure `graphGrouping` module produces scoped group keys, + parent/child membership, display names and diagnostics. Each source node has + one primary owner per active lens; overlapping tags are secondary facets. + Detect missing parents, cycles, duplicate keys and depth overflow. Preserve + affected nodes in visible diagnostic/unassigned groups. Rebuild on relevant + data/lens changes, not wheel events. +- **Count-preserving projection.** Pure `graphProjection` maps each authorized, + in-scope node to a visible representative. Aggregate edges by source + representative, target representative, type and direction. Keep underlying + IDs addressable; internal edges become inspectable counts. Use an exclusive + accounting ledger: rendered individual, aggregated, internal, explicitly + filtered, or pending endpoint. Viewport culling is a presentation attribute, + not another source-edge category. No silent loss or double counting. +- **One LOD policy.** `semanticLOD` consumes projected dimensions/density and + existing adaptive-quality signals. Quality may lower effect/primitive budgets, + but it must not erase the last readable group identity. Replace conflicting + thresholds gradually; retain edgeLOD helpers where useful at item detail. +- **Readable labels.** A screen-space label layer with deterministic priority + and collision handling; group titles ≥14px and item names ≥12px. Budget names + by available space (initially 24–40 desktop, 6–10 phone). Reserve focus/search + and blocker labels; expose overflow explicitly. Obstacles include headers, + floating controls, inspector and minimap. Verify hit-testing as well as bounds. +- **Stable motion.** Start with 15–20% hysteresis and 150–250ms crossfades, tuned + against real wheel/pinch input. Freeze expensive membership computation during + a gesture. One representation owns input during transitions; reduced motion + uses immediate stable swaps. Keep the zoom anchor stable across expansion. +- **Separate coordinates and writes.** Summary anchors and temporary expanded + layout are view coordinates. Preserve saved item positions and manual pins. + Zoom, fit, collapse, filters and view restoration enqueue no node-position + mutation. Drag/explicit layout commands retain the existing save queue and + error handling. Synthetic summary IDs can never reach CRUD/undo mutations. +- **Scoped view state.** Versioned camera/lens/expansion/selection state keyed + by owner + graph, with pruning on deletion and permission loss. Use local view + persistence first; portable saved views require explicit format support. + It does not replace the pending-edit durability work in #181. +- **Bound work, not just opacity.** Render only the chosen projection, with + overscan. Cache indices and aggregates; coalesce camera changes once per + animation frame. Run costly grouping/layout in a cancellable worker if needed; + reject stale results after graph/account/lens changes. Avoid force reheats on + zoom. Retain SVG for the first useful slice. Decide Canvas/WebGL from profiles + after projection; preserve accessible DOM controls and hit-testing if changed. + +## 4. Ordered delivery backlog + +Every stage is a reviewable PR or small PR series. Tests below extend existing +Vitest/Playwright/unified-report infrastructure. Keep stages open until their +acceptance evidence exists; the names do not imply an issue has been created. + +| Stage / proposed title | Deliverable and boundary | Depends on | Exit evidence | +| --- | --- | --- | --- | +| **LOD-0 — Establish the current semantic-zoom baseline** | Port the useful October capture helpers onto current dev without old runtime code or private captures. Add a synthetic release-shaped fixture and a generic subsystem fixture. Add complete read-only request guarding and safe operation diagnostics; reproduce the position-write attempt on owned data. Pin hardware, target graph identity and budgets. | None | Missing-data, attempted-write and missing-metric negative controls fail. Desktop/phone baseline retained. Diagnose or explicitly isolate pre-existing layout autosave before declaring a clean live review. | +| **LOD-1 — Normalize grouping and conserve graph identity** | Typed metadata adapter, stable hierarchy and projection modules; graph revision and exact/partial count semantics. Support release lanes and generic nested group paths. Build directed/type-aware aggregate edges from the outset. | LOD-0 fixture contracts | Unit/property tests conserve nodes/edges, survive bad metadata/cycles, preserve antiparallel and mixed-type edges, and round-trip expand/collapse. | +| **LOD-2 — Ship a readable overview in the real renderer** | Render named summary primitives and basic typed connectors; scoped counts, blocker indicators, group click/keyboard focus, breadcrumb and back. Retain the existing detail renderer and inspector. Feature flag permits comparison/rollback. | LOD-1 | Real localhost and synthetic release-shaped overview have readable groups and all explicit blocking relationships discoverable at fit scale; summary drill-down reaches exact items; no saved-position writes during navigation. | +| **LOD-3 — Make detail continuous and locally adaptive** | Screen-space labels, compact items, local density/quality budgets, hysteresis, transition ownership, selection/search retention, inspector-aware fit, touch and keyboard behavior. | LOD-2 | Repeated 0.44↔0.46 and 0.19↔0.21 crossings produce no anonymous interval, focus loss or uncontrolled card explosion. Dense and sparse regions can use different representations. All label/hit-target floors pass. | +| **LOD-4 — Complete relationship exploration** | Exact bundle drill-down, internal-edge inspection, focused 1–2 hops, upstream/downstream convention, named offscreen ports, complete accessible relation list and filtering denominators. | LOD-2; integrate with LOD-3 | Every source edge is accounted for; selected paths remain traceable through collapse; exact IDs/types/directions return from bundle expansion; cycles/self-loops/100+ degree hubs are navigable. | +| **LOD-5 — Support AI-authored systems and unknown schemas** | Document an optional versioned grouping/display metadata contract and an AI-authoring example. Preserve canonical titles/provenance. Validate import/export/MCP/API round trips. Add deterministic suggested grouping for graphs without metadata; rename/pin/split/merge overrides survive updates. | LOD-1; UI uses LOD-3/4 | A nested software system and an unannotated topology graph both support overview→detail→back. Imported IDs/group references remap correctly. Malformed AI payloads retain recoverable data and explain errors. No inference/remote summarization runs on zoom; bounded, coalesced and cancellable graph-data requests may serve a newly visible region. | +| **LOD-6 — Enforce 1k scale and bounded rendering** | Source-registry caching, adjacency/spatial indices, projected DOM budget, metrics for projection/labels/frame latency, cancellable workers where profiling requires. Remove repeated full-graph scans from gesture work. | LOD-3/4; generic fixtures from LOD-5 | Required 1k/5k-edge desktop and mobile journeys pass with exact source counts and no sentinel metrics, retries or required skips. Publish CPU/layout/paint traces and before/after measurements. | +| **LOD-7 — Load and navigate 10k graphs without full hydration** | Replace the obsolete streaming assumptions with an authorized overview/neighborhood/page contract; bounded first paint, summary-first loading, cancellable requests, stable cursors/revision, pending endpoints and server-side search. Integrate import/storage admission limits. | LOD-1 source semantics; full UI on LOD-6 | End-to-end 10k/50k graph loads a truthful overview, searches an unloaded item, expands its region and follows edges without downloading all descriptions first. Missing/delayed/duplicate/stale pages cannot erase data or claim completeness. | +| **LOD-8 — Prove cross-backend compatibility** | Cloud-owned adapter and capacity policy; align authorized summary/page/search contracts, Core submodule pin, offline/import behavior, metadata round trips and bounded persistence. Preserve conservative public guest admission separately from larger supported accounts/operators. | LOD-5/7; #182 acceptance infrastructure | Owned Core and hosted/staging browser journeys return equivalent memberships, relationships and selection. Real persistence/account-isolation readback; visible limit errors and recovery. Exact frontend/API/schema versions recorded. | +| **LOD-9 — Release and close the feature** | Reconcile current base, complete review/gates, document controls/limits, rollout flag, verify installed artifacts and rollback. Move the canonical design/story status with the implementation. | LOD-0…8; applicable #189/#190 path | All definition-of-done rows below satisfied on the delivered build; remaining enhancements separated. Merge/preview alone cannot close this stage. | + +The shortest first-useful path is **LOD-0 → LOD-1 → LOD-2**. The next milestone is +**LOD-3 + LOD-4 + LOD-5 → LOD-6**. API contract design for LOD-7 can proceed once +LOD-1 establishes identity/count semantics; the completion gate remains after +the integrated UI. Cloud planning can start early without changing deployment. +Avoid a wholesale rewrite of the 6k-line renderer as a prerequisite: introduce +the projection boundary, then extract the layers touched by each slice. + +### The first implementation PR + +Start from current dev, rechecking upstream and active ownership. Bring the +read-only review helper and its tests forward with source/build provenance; +keep the static walkthrough as a reference artifact. Add sanitized fixtures, +normalization/grouping types and conservation tests. Extend shared WorkItem +typing rather than hiding metadata parsing in SVG callbacks. Keep runtime +behavior behind the existing renderer boundary until LOD-1 invariants pass. + +The first renderer PR then consumes that projection for overview + basic +relationships + drill-down. Do not spend this first slice on the release matrix, +automatic prose summaries, a WebGL migration, or a fresh test platform. + +## 5. Large-data and AI authoring contracts + +The June streaming proposal's offset/dedup sketch is insufficient for mutable +large graphs and exact totals. Specify an authorized, revision-aware API in +LOD-7 (names such as `graphOverview`, `graphNeighborhood`, `graphPage` are proposed): + +1. Overview returns group identity/hierarchy, exact authorized counts, bounded + typed aggregate connections, freshness/revision and completeness status. + It must not fetch every long description merely to compute the overview. +2. Neighborhood/member pages return stable IDs, cursor/tie-breaker and revision; + cancel obsolete requests on graph/lens/account change. A stale page cannot + replace a newer edit or expand a graph the user has left. Concurrent edits + require revision restart/reconciliation, not a false “all loaded” badge. +3. Server-side search returns exact matches plus their grouping path and a way + to fetch the selected item; client-only search cannot cover unloaded data. +4. Source storage explicitly models pending endpoint edges and deletions. Reuse + D3 identity merge on the reconciled loaded set, never on a single page. +5. Counts distinguish total authorized, filter-matched, loaded and visible. + Partial counts are labeled. Do not derive a total from the current page or + leak inaccessible node existence through summary counts or edge endpoints. +6. Authorization filters precede aggregation. Cache keys include access scope, + graph revision and grouping/filter schema. Revoked access clears the affected + registry, view state and pending requests. Test cross-account negative cases. +7. Query cost, response bytes, cache size, concurrency and import size are bounded. + Respect Save-Data/low tiers; expansion works on demand. Error/offline/loading + states retain existing context and do not become “empty graph.” + +For AI-produced graphs, begin with a documented optional namespace inside the +existing metadata envelope: schema version, stable semantic group path/key, +display alias and provenance. Final field names are part of LOD-1/5 review. +Unknown metadata is preserved. Primary membership is deterministic; multiple +lenses do not multiply counts. Group references must use portable semantic keys +or be explicitly remapped with fresh node IDs on import. The current opaque +metadata string does not automatically remap embedded node references. + +Provide one validated JSON example and one API/MCP creation example for a +system→service→component hierarchy with cross-service typed edges, cycles, +unassigned items and long names. Confirm the actual importer and server support +those contracts; merely documenting a payload is not acceptance. Any schema +change must retain existing version-1 import/export compatibility or offer an +explicit versioned migration. Large import limits must be reconciled across UI, +offline store, portable validation and backend admission. + +Core owns portable semantics, renderer, agent authoring contract and tests. +Cloud owns hosted storage/admission, adapter parity and deployment evidence. +Devops changes are needed only if packaging/observability requirements emerge; +LandingPage changes follow demonstrated capability and documented limits. + +## 6. Validation and performance gates + +Extend `edgeLOD`, `parallelEdges`, `graphDataMerge`, `adaptiveQuality`, adjacency +and projection unit suites. Add a dedicated semantic-LOD browser suite within +the current graph/quality projects and report manifest, using existing seed, +auth, cleanup, screenshot and diagnostic helpers. Port the live review as opt-in +read-only evidence; never run mutating scale fixtures against the user's graph. + +| Layer | Required cases | +| --- | --- | +| Pure invariants | Unique node coverage; exclusive edge accounting; stable IDs/order under reordered input; nested/cyclic/missing membership; self/parallel/opposite edges; internal counts; filter denominators; expansion round trip; partial pages and deletions | +| Small regression | 0/1/12/50/150/151 nodes; existing create/edit/delete/grow/drag/undo, subgraph navigation, saved positions and small-graph readability | +| Real shape | Sanitized prior 299-node shape and refreshed 301-node shape; all source entities and blocking relationships preserved; long imported titles, pinned lanes, hubs and supporting entity types | +| Generic systems | Nested explicit subsystems; containment-only IS_PART_OF/CONTAINS hierarchy; conflicting metadata/containment and multiple parents; metadata-free communities; disconnected islands; dense/sparse areas together; multilingual/long labels; high-degree hub; cyclic typed dependencies | +| Scale | 500/1k/2k/10k nodes with recorded edge counts/density; 1k/5k and 10k/50k strict targets; placed and unplaced layouts measured separately | +| Interaction | Fit→expand→select→collapse→return; search in collapsed/unloaded group; repeated threshold crossing; pan to offscreen port; resize/inspector; wheel/pinch/tap/keyboard; reduced motion; browser zoom and device-pixel ratio | +| Live updates | Added/removed/moved items, metadata changes, delayed acknowledgements, deleted selection, stale worker/page response, account switch/revocation, graph switch during expansion | +| Persistence | Zero node writes for navigation; real edits still use save recovery; owner-scoped view restoration; import/export identity mapping; no synthetic summaries persisted as nodes | + +Proposed acceptance budgets, to freeze in LOD-0 on named hardware/browser: + +- Readable summary titles ≥14 screen pixels; item labels ≥12; ordinary touch + targets ≥44px. No overlap/occlusion of required labels or pointer interception. + Test actual visible text and ordinary clicks, not DOM existence or forced clicks. +- At 1k/5k, p95 input-to-next-frame <50ms; aim for ≤16.7ms steady desktop pan + frames and ≤33.3ms on the mobile profile. No >100ms main-thread task caused by + an LOD transition. Report median/p95/worst, not only average FPS. +- At 10k/50k on the desktop profile, p95 input-to-next-frame <50ms after overview + availability, using bounded projection; do not relax readability to meet time. + Record overview load, detail load, API time, transfer bytes, DOM primitives, + projection/label time, heap and retained cache separately. +- Preserve the ADAPT-4 target of useful first paint <2s for 1k on explicitly + configured simulated 3G; define latency/bandwidth/cache settings in the fixture. + For 10k, proposed useful overview <3s on the documented normal-network profile. + “Useful” requires readable summaries and counts, not a spinner or first dot. +- Repeated full zoom/expand/collapse/graph-switch cycles must plateau in retained + DOM/cache/heap after cleanup. Set concrete memory caps from the LOD-0 profiles; + no numeric heap limit is claimed as measured by this review. +- Existing strict smoke, mobile, layout/durability, unit/harness and required CI + gates stay enabled. Required LOD cases have zero skips/retries/flaky passes; + missing or stale metrics fail. Keep failure evidence and fixture cleanup results. + +Retain exact source/lockfile, served build identity, backend/schema/graph revision, +browser/hardware/viewport/DPR, fixture seed and edge density, flags, network/CPU +settings and projection counts with results. FPS from this workstation does not +establish phone performance. The current host is Ryzen 9 3900X / 24 logical CPUs; +headed GPU rendering and headless/software rendering must be labeled separately. + +Run task-based human checks at the 301-node and generic-system milestones: +identify a subsystem within 10 seconds, locate a blocker and inspect its source +within 30 seconds, follow a cross-subsystem dependency, and return without losing +place. Record correct/incorrect answers and navigation failures; screenshots +and geometry checks alone cannot establish comprehension. + +## 7. Definition of done and rollout + +- [ ] Real localhost and generic AI-authored graphs remain named and navigable at + every detail band; automatic zoom and manual overrides agree. +- [ ] Selection/search/keyboard/touch and typed relationships survive every + collapse/expand/filter boundary; exact source data remains inspectable. +- [ ] All node/edge accounting, metadata/import and partial-loading invariants pass. +- [ ] The 1k/mobile and 10k/desktop targets pass on documented profiles, including + first useful load and bounded rendering, rather than only a cached static demo. +- [ ] Navigation changes no saved positions; actual edits retain existing recovery + guarantees. LOD does not silently weaken #181 or import/offline compatibility. +- [ ] Hosted supported limits and behavior are explicit and tested with real + account-isolated persistence. Any hosted large-capacity gap remains open, + rather than being hidden by a Core-only success. +- [ ] Source review and current required gates pass; released/installed frontend + and backend versions are verified. Documentation, stories and regression index + are updated with actual evidence and remaining limits. +- [ ] Rollback is proven: disable the feature or restore the prior tested build + without rewriting source graph data or destroying saved user layouts. + +Roll out behind a developer/test flag, validate owned fixtures, then conduct a +guarded review of the real localhost graph. Enable for supported large graphs +after acceptance; retain the manual detail control and accessible relation list. +Ship the exact reviewed Core revision through each installation's existing +release path. Recheck #191/#189/#190 rather than assuming their October status. + +The first implementation below advances LOD-1/2 on current dev. The feature +remains open until the checklist above has evidence on the delivered application. + +CodeGraph-informed refinement: LOD-0 includes intermediate-zoom coverage, label +self-resolution and stable-page/camera tests. LOD-1 adopts parent fallback and +explicit page accumulation/epochs from the outset. LOD-3 applies geometry/transition ideas in GraphDone's own implementation; LOD-7 +adapts generation-bound, coalesced loading. Domain-specific edge projection and +GraphDone edit/persistence behavior remain explicit responsibilities. + +Owner clarification (2026-10-27): reuse ideas in both directions with CodeGraph; +keep implementations independent. No shared libraries or common package. + +## 8. First production-renderer slice (2026-10-27) + +See the [validation record and screenshots](../quality/DYNAMIC_LOD_VALIDATION_2026-10-27.md) +for exact revisions, passing checks, retained failures and remaining integration gates. + +`packages/web/src/lib/semanticLOD.ts` now projects loaded source items into +top-level groups. Precedence is explicit `metadata.lod.groupPath` (or +`metadata.groupPath`), native containment, release `lane`, `kind`, then item type. +Metadata may be a JSON string or an object. For example: + +```json +{"lod":{"groupPath":["Payments","Settlement workers"]}} +``` + +This initial cut displays **Payments**; nested spatial expansion is still pending. +Containment directions are normalized, cycles terminate deterministically, and +multiple-parent/conflicting-path diagnostics remain visible in the group list. +Every loaded relationship is accounted for as an internal link, a typed directed +bundle, or a pending-endpoint edge. Canonical items/edges are retained unchanged. + +`SemanticGraphOverview` is integrated into `InteractiveGraphVisualization`, not a +standalone preview. Graphs with at least 40 loaded items offer Auto, Overview and +Items. Auto retains summaries through a hysteresis band. Group labels use fixed +14px screen-space text, avoid current graph controls, and keep their slots during +camera gestures. Leaders connect labels to source centroids; arrows show bundled +directions. The group browser supplies exact typed counts and full member titles. +Groups that cannot fit on a phone remain available through the complete group +list. Inspecting a member selects the original item in the existing inspector; +Back restores the entry camera and detail mode. + +The summary layer is a React-owned SVG `foreignObject` inside the existing D3 +camera surface. Native wheel, mouse and touch gestures use the existing zoom +behavior. D3 owns only its sibling graph layers and direct-child definitions. +Source layers are removed from painting/hit testing while Overview is active; +projected coordinates never enter the source simulation or position-save queue. + +This is a useful **overview and navigation slice**, not completion of LOD-2/3. +It still loads and constructs the full source SVG. The next implementation work +is a nested spatial cut with stable group anchors, continuous readable transitions, +selected/search-path expansion, per-region density budgets, and typed relationship +inspection. Type fallback is explicit, not claimed topology clustering. Streaming, +worker cancellation, import limits, Cloud parity and the 1k/10k capacity gates +remain open. The existing autosave behavior for newly laid-out or overlapping +source items is outside this slice; zero-write navigation is tested on pinned, +non-overlapping owned fixtures rather than asserted for every legacy graph. + + +## 9. Nested hierarchy continuation (2026-10-28, #215) + +The current candidate consumes every explicit path segment and normalized native +containment as a deterministic hierarchy, with display-only cuts at any supported +depth. Stable semantic path keys and canonical source IDs are separate. Children +replace a parent only as a complete partition; items directly in an expanded +parent have a separate direct-items representative. Cycles, multiple containers, +invalid segments and depth beyond 32 retain exact source coverage and expose +visible diagnostics. Every typed directed relationship remains internal, bundled +or pending its endpoint; self-links and parallel relationships are not discarded. + +The production group browser supports subgroup expansion, ancestor collapse and +complete original member inspection. Manual Overview starts at the roots. Auto +uses zoom hysteresis and the available non-overlapping screen-space label slots: +when all child representatives cannot fit, their named parent remains visible. +Selection stays attached to the original item and its current summary; Back +restores the entry camera and cut. Membership is memoized separately from camera +movement, and label slots stay fixed during a gesture within the same cut. + +This adapts CodeGraph's parent fallback, independent camera and stable hierarchy +ideas within GraphDone. It adds no shared library and changes no CodeGraph source. +It advances LOD-1/2 and the transition portion of LOD-3. It does **not** complete +per-region projected geometry/density, source DOM virtualization, relationship +inspection, AI import/export/MCP round trips, progressive loading, or hosted parity. +All source SVG nodes are still constructed, and graph data is fully loaded. Global +zoom bands and a label-slot budget are not evidence of measured 1k/10k capacity. + +Next bounded slices remain LOD-4 exact relationship exploration, LOD-5 portable +AI-authoring round trips, then LOD-3/6 region budgets and measured bounded rendering, +followed by LOD-7 authorized revision-aware loading and LOD-8 hosted acceptance. +The completion checklist in section 7 remains authoritative. See the +[nested validation ledger](../quality/NESTED_LOD_VALIDATION_2026-10-28.md) for +failing-before evidence, candidate checks and explicit remaining gates. + +## 10. Schematic visual direction — 2026-10-28 (#219) + +The owner requires harness/PCB-style visual organization: clean ports, routed +wires, explicit buses and small regression fixtures that reject spaghetti. +The [schematic connection plan](schematic-connections.md) governs the next visual +slice alongside #218 exact relationship inspection. Nested cuts in #216 remain +useful foundations, but do not establish visual completion. Item-level routing, +acceptance of the new focused group trace, generic imports and the scale/loading +gates remain open. The candidate pages incoming/outgoing neighbors with honest +not-drawn counts; it does not yet trace detailed item paths. + +Local full-stack test batches are paused under the workstation constraint after +the reported desktop crash. Use small serial geometry/component checks and +static contact sheets locally; broad integration/browser/image matrices run in +CI. Retain failures and interruptions honestly. Do not change the owner's local +graph or represent an empty/mostly hidden diagram as proof of clear routing. diff --git a/docs/architecture/local-item-lod.md b/docs/architecture/local-item-lod.md new file mode 100644 index 00000000..78109240 --- /dev/null +++ b/docs/architecture/local-item-lod.md @@ -0,0 +1,13 @@ +# Readable local detail for dense connected items + +Items mode must remain useful around a high-degree focus. EPIC2215 exposes the failure:107 essential neighbors,107 long wires,zero visible neighbors. Showing every full item at a global ring radius cannot satisfy readable detail. + +A local projection retains the original focus and a bounded page of real neighbors. Use current semantic paths to summarize overflow, separate incoming/outgoing buses and preserve every original directed/type/parallel relationship. The existing relationship-priority projection chooses represented types per original pair; spatial aggregation then makes that representation fit. Counts distinguish represented, priority-summarized, spatially aggregated/deferred, self/internal and missing-endpoint relationships. Browse always resolves current original IDs. + +Activation is automatic for a visible dense focus in Items mode. Explicit Focus or overview/search navigation chooses the local focus; opening an original inspector or inspecting a relationship preserves the working cut. Otherwise use the relevant dense item near the viewport center. Retain that focus and deterministic page/group selection through small camera changes. A focus change, meaningful viewport exit, resize or actual data change can refresh the cut. Small direct graphs retain the native renderer. Auto overview continues to own low-zoom summaries; manual Items still uses readable local detail when needed. + +The local layer owns separate screen positions. It never changes canonical node positions, simulation membership or saved layout. Projected item cards open the existing original-item inspector. An explicit saved-position edit returns to the canonical board before dragging. Pan translates the accepted view and zoom controls its LOD transition; pointer hover and ordinary camera frames do not rebuild routes. Navigation/Back restores focus, page, group filter and camera; reload derives the useful cut without saving positions again. + +Cards use measured title/type/status dimensions and14/18px typography. Control geometry determines capacity, with a small hard cap; admit at least one real neighbor when two cards fit, and show named overflow/paging access honestly. Use the in-repo schematic router and accessible bus interaction layer. Every painted endpoint is a painted card/summary, with generous card/wire/control clearance and meaningful visible connection coverage. Phone geometry may require a smaller page, never microscopic text. + +Validate the actual owned301 case, deterministic107-neighbor/incoming/reverse/parallel/self/missing/live-membership cases, actual editor/bus/keyboard behavior, no-mutation snapshots and hosted2k/5k scale. Report bounded admission/routing work and every deferred original. Keep the earlier failing headed evidence. Do not call233/220 complete while this focused dense view is still a fan of offscreen wires. diff --git a/docs/architecture/schematic-connections.md b/docs/architecture/schematic-connections.md new file mode 100644 index 00000000..96fac2ac --- /dev/null +++ b/docs/architecture/schematic-connections.md @@ -0,0 +1,306 @@ +# Schematic connections and visual acceptance + +Owner direction recorded on 2026-10-28: GraphDone should be legible like a harness or +PCB schematic, with a modern visual style. A tangled collection of curves is not +an acceptable large-graph overview. This continues the dynamic LOD plan in #219 +and exact relationship inspection in #218; CodeGraph techniques remain ideas, +not a shared library or shared release dependency. + +## Product rules + +- Place readable groups first. Wires route around cards, their labels and the + graph controls, with intentional ports and orthogonal lanes. +- Multiple types and parallel original links from the same source to the same + target share a drawn branch. Branches from the same source may share a trunk; + junction dots distinguish that intentional bus from unrelated crossings. +- Arrows follow the original source-to-target direction. A bus is display-only; + it never invents a canonical relationship, changes a type, or implies every + member of a group is connected to every member of another group. +- Inspect a group, its internal links, a directional bundle or an item-centered + one/two-hop neighborhood to see exact IDs, titles and types. Parallel links, + self-loops and unresolved endpoints remain distinct. Incoming/outgoing refers + to stored direction, not a guessed dependency execution order. +- Dense scenes disclose connections that cannot be routed clearly. The complete + list stays reachable. A named incoming/outgoing trace pages through connected + groups without turning every connection back on; all outside connections stay + counted and accessible. +- Camera movement keeps screen-space lanes and ports fixed while the cut and + viewport remain unchanged. Source positions are sampled for placement when + that placement changes, not rescanned for each pan event. Inspection and Back + preserve the camera, hierarchy cut, Auto hysteresis and relationship context. + +## Candidate implementation + +`schematicRouting.ts` groups directed connections and searches an obstacle-aware +orthogonal grid. It refuses card intersections and contact/overlap with unrelated +buses. Shared source trunks have a reduced route cost, favoring readable fan-out. +Original edge IDs partition exactly into routed or explicitly deferred branches. +Routing has one invocation-wide attempt/state allowance, including failed +searches, plus deterministic branch/grid limits; no worker, new dependency +or graph mutation is introduced. Grid/card budgets are limits of this candidate, +not evidence of 1k/10k graph capacity. + +The overview uses the router instead of its former direct cubic bundle curves. +Always-visible centroid leader lines are removed: they looked like additional +relationships and could cross labels. Source location remains available through +inspection and exact camera return. A fixed-size Connections control avoids a +feedback loop where a changing count moves labels and changes routing again. + +`SemanticRelationshipBrowser` uses the canonical edge objects and adjacency +indices. Stable hierarchy selectors resolve current membership and headings when +items move or are added; a disappeared group is explicitly unavailable. Type/text filters disclose their denominator. Source inspection returns +to the relationship context; missing endpoints cannot be clicked as if loaded. +These controls still require strict real browser/database acceptance in CI. + +The group browser now offers incoming and outgoing connection traces. A trace +keeps its selected group and at most five connected neighbors on a page, bounded +further by available label slots. Stable neighbor ordering makes every connected +group reachable. Paging, direction changes and live removals retain truthful +counts; a removed selected group is explicit. Incoming branches share their +actual target trunk without reversing stored source/target identities or arrows. +The current cut is held in manual Overview while tracing; item inspection and +Back retain the trace. Exiting returns to the same cut. If fewer than two slots +fit, the selected group takes priority and the complete list remains available. + +Controls are measured after commit so a newly mounted trace bar is a routing +obstacle immediately. Camera pans do not rescan source positions or reroute the +unchanged trace. This remains group-level exploration; internal/item-level wires +and arbitrary selected multi-group paths still need their own implementation. + +## Lightweight visual gate + +Run on supported Node 22.18+ without installing a browser or starting a database: + +```sh +npm run test:schematic +npm run report:schematic -- --out test-artifacts/schematic-routing +``` + +The tests are serial and the first four-fixture run took about 0.3 seconds locally. They +cover a chain, a fan-out bus, a dense cyclic graph, phone geometry and unplaced +endpoints. A deliberate spaghetti negative contains a card/label intersection, +unrelated crossings, overlapping wires and a diagonal segment; the gate must +reject it. Each accepted fixture must retain useful visible connections and +account for every original edge, including explicit deferred connections. + +Metrics currently require zero wire-through-card intersections, zero unrelated +bus contacts/overlaps, orthogonal segments, and at most four distinct source +buses in a 48 px cell. Those are initial review thresholds, not universal layout +quality or performance claims. Input reorder must preserve the route result. +Shared-bus junctions must be visibly marked. Rendered SVG contact sheets and JSON +metrics come from the same module used by the application and are retained by CI. +The CI-only `schematic-visual` sequence also runs four headless, one-worker +fixtures with zero retries, real SVG text bounding boxes, no network requests, +and retained screenshots. It is enrolled in PR/full profiles and the Smoke Gate; +it does not start another app or database. Browser acceptance is pending until +the actual report passes. + +Review the images as well as the numbers: metrics alone cannot establish pleasant +spacing, readable names or a useful hierarchy. + +## Remaining work before visual completion + +1. Review the actual overview at desktop/phone sizes, add long/multilingual names + and obstacle fixtures, and test expanding a routed bus into its exact links. +2. Accept the selected-group trace in real desktop/phone CI, inspect its pictures, + and extend it to selected item paths. Group paging alone is not full path tracing. +3. Carry organized ports/routing into item/neighborhood wiring; the existing + item renderer still uses its previous edge geometry. Preserve drag/edit/save + behavior and use a suitable detail budget rather than drawing every link. +4. Pass the new actual overview screenshot/DOM geometry and direction checks in + the existing CI journeys, then measure larger fixtures there. Local full-stack and image matrices are paused + under the owner's workstation constraint; never call skipped checks passed. +5. Reconcile reviewed nested hierarchy #216, this visual slice, import/MCP #217 + and the remaining LOD scale/loading/hosted requirements before release. + +This is a reviewable candidate path. It does not close dynamic LOD, hosted parity, +release/deployment work, or the remaining item-level visual problems. + +## Detailed item wiring continuation (#220) + +The first item-wiring increment starts from dev +`007148f1a526b8ef028222a429b346a67e5c3c2c`. `edgeRouteGeometry.ts` defines the +shared display path, final-segment arrow, distance along the route and pointer +projection needed by the D3 path/hitbox/arrow/label consumers. Consecutive +coincident points do not produce broken paths; a nonzero closed loop retains +its travel and direction. Label placement fits an entire label on one segment, +keeps text upright, retains original icon direction and returns no placement +when no sampled slot clears cards or previous labels. This is conservative +admission, not proof that every possible position is obstructed. Equal-distance pointer hits +prefer the prior leg so dragging does not jump across a nearby return wire. + +The next committed increment connects this geometry to the D3 path, hitbox, +arrow and label consumers. It still supplies straight border-to-border paths; +this does not establish routed item acceptance. Next add bounded routing and +admission, named boundary connections, exact deferred counts and accessible +original relationships. Routing and obstacle searches belong to +bounded updates after layout or scope changes, not every camera/simulation tick. +Preserve edit/Grow, pointer hitboxes, label dragging and canonical positions. + +On 2026-10-28, Node22.18 ran the nine pure geometry cases serially in the existing +clean-installed partial probe: 9 passed, no skips, 0.69s (8ms test bodies). +Cases cover elbows, reversed segments, closed loops, duplicate/invalid points, +nearest-leg dragging, ties, long labels and rotated obstacle footprints. This +is scoped helper evidence, not a full-tree browser/database/performance result. + +Independent foundation review also required offset-aware dragging beside close +return wires. The executed negative selected fraction100/440 instead of340/440; +matching projection to the displayed offset corrected it. All ten cases passed +serially in0.691s and scoped strict ES2020 types passed. Candidate interpolation +now stays on its current segment and keeps only the best placement, avoiding +repeated route scans/sorting. The [canonical lesson](../../.agents/inbox/2026-10-28-offset-label-drag-needs-offset-path.md) +records the negative, correction and runtime integration limits. + +The D3 adaptation caches label dimensions after initial text sizing, reuses the +path for paint and hit testing, places arrows from its final segment and slides +labels along its displayed offset. Culling composes with route/label visibility +instead of revealing deferred geometry. Label dragging updates only that label +against cached obstacles. The interaction diagnostic clicks the actual path +midpoint; the center of a bent path's bounding box need not lie on the path. + +Scoped web typecheck initially rejected a D3 nullable-style overload; the callback +form corrected it, and the same check passed in3.66s. Scoped lint returned +0 errors and298 warnings in the existing large renderer; no warning-free claim. +These checks used the partial probe and are not actual browser acceptance. +All original runtime/visual/durability gates and new item-wiring cases remain +required before this branch can be integrated or #220 closed. + +Review of the first consumer commit (`d9a0a58`) found empty measurement of +initially hidden labels and a line-only geometry diagnostic. The correction +initializes all label parts from conservative dimensions and accepts only +positive text measurements; diagnostics now read actual SVG path endpoints. +Opposite-direction parallel edges share a canonical lane normal so they stay +separate while their arrows retain direction. Wire and label clicks open the +same original relationship editor, whose controls now have accessible names. + +Two real-browser cases are registered as blocking `item-wires` acceptance in +CI immediately after smoke (one worker, zero retries), retaining actual +paint/hitbox/arrow geometry and screenshots, pointer drag/edit behavior, hidden +label dimensions and unchanged graph data. Local discovery found both cases; +they have not run locally. The partial probe passed11 geometry cases in0.722s, +32 CLI harness cases in4.56s and scoped types; scoped lint reports0 errors, +314 warnings across the two components/helper/test. The +[review lesson](../../.agents/inbox/2026-10-28-hidden-labels-need-valid-measurement.md) +records the evidence and limits. These are straight item paths preparing the +bounded routing increment, not completion of #220. + + +## Relationship priority with connection fallback (#220, 2026-10-28) + +The owner wants a selectable relationship-priority level, while retaining at +least one connection when a pair has no relationship at that level. This is the +next bounded presentation increment after readable-symbol #222/#223; it does +not change canonical edge types, weights, source/target IDs or hierarchy. +The group-wiring #216 and item-geometry foundation #221 are accepted on dev; +full item obstacle routing and buses still remain #220. + +Use two independent controls: **Detail** chooses semantic/item representation; +**Relationships** chooses the amount of relationship meaning shown. Start with +three named priority stops, with readable labels, keyboard/touch support and an +explicit current value. A manual relationship choice must persist through zoom +and inspection/Back. Do not make a tiny unlabeled slider or silently switch the +owner's chosen level as the camera moves. Auto priority may be considered later, +after the manual behavior is accepted. + +Proposed initial stops are **Essential**, **Dependencies**, and **All**. Treat +type ranking as an explicit presentation policy, not a claim that business +importance can be inferred perfectly from type. Essential promotes `BLOCKS` and +`CONFLICTS_WITH`; Dependencies also includes `DEPENDS_ON`, `ENABLES`, `FOLLOWS`, +`VALIDATES` and `SATISFIES`; All includes structural/reference/general relations. +The policy needs a conservative known default for unknown imported types and +one central mapping used by item and group projections. Containment still drives +hierarchy regardless of the relationship stop. Do not reuse node priority or +edge weight without a documented semantic contract. + +The fallback is **per connected endpoint pair**, not one arbitrary edge in the +whole graph. For each original pair, retain relationships admitted by the +selected level; if none qualify, retain its highest available tier. Keep ties +with distinct directions/types in the represented connection, rather than +silently dropping a cycle or deciding by incoming array order. Repeated copies +can share a presentation with a count; their original IDs remain separately +inspectable. A pair connected only by `REFERENCES` must still have a visible +representation at Essential. Deleting the last blocker must immediately reveal +the pair's dependency/reference fallback. Increasing detail must add meaning, +not unexpectedly remove already represented relationships. + +Use priority to select the primary label/color of a shared branch or bus before +spending route/label budgets. For `A BLOCKS B` and `B DEPENDS_ON A`, emphasize +`A blocks B` and expose both original relations through its count/inspection. +`A BLOCKS B` and `A DEPENDS_ON B` have different implications; filtering the +latter by an explicit priority stop is not semantic deduplication. Never rewrite +its arrow or state that the records are equivalent. At All, exact types and +original directions must be available in the drawing's expanded inspection. +Opposite blocking links and self-links remain meaningful. This is pair-local +presentation reduction, not transitive reduction of a dependency graph. + +At group scale, apply the pair policy before aggregation so one blocker elsewhere +between two groups cannot erase unrelated lower-tier member-pair coverage. +Then aggregate represented pairs into bounded, count-preserving buses. All +original records partition into directly represented, priority-summarized, +internal, missing-endpoint or geometry-deferred categories with non-overlapping +counts. Prefer a compact primary label and a count over multiple competing tiny +labels. Inspection reveals why a representative was chosen and every original +ID/type/direction. Missing endpoints must not masquerade as routable pairs. + +The fallback guarantee is a projection invariant; finite screen space still +requires explicit geometry deferral. Do not force an illegible wire through a +card to satisfy it or claim a hidden/deferred pair is drawn. A connected pair +must remain reachable through a named bus/boundary representation or complete +inspection with truthful deferred counts. Routing should prioritize blocking +branches before contextual ones while reserving useful neighborhood coverage. + +Before enabling the control, test: blocker plus reverse dependency; same-direction +non-equivalence; references-only fallback; ties/opposite directions/self-links; +unknown types; live deletion of a winner; deterministic input reorder; monotonic +level changes; group aggregation retaining every member-pair; selected-edge/edit +access; zoom/Back persistence; and unchanged API snapshots. A small headed +parallel-edge fixture should show the primary label replacing redundant visual +labels, with all original records still accessible. Extend the2000-item gate to +exercise the stops, meaningful coverage and conserved counts. Keep the existing +clutter/typography budgets and deliberate spaghetti negatives. The type-tier +mapping and control labels are implemented in the candidate below; final visual +review and acceptance are still required. + +The first priority candidate adds `relationshipPriority.ts`: canonical endpoint +pairs retain every original ID and direction; the selected stop admits all ties +up to its tier, or the best available tier when none qualify. Unknown imported +types use the contextual tier and remain available through pair fallback and +All. Missing endpoints stay a separate pending partition. Applying its admitted +IDs to canonical semantic bundles retains other lower-tier member pairs and +does not rebuild containment. The candidate now wires this projection into item visibility, primary labels, +label admission and semantic bundles while retaining canonical data. + +On 2026-10-28, eight serial Vitest cases passed in 0.877s using the existing +owned dependency fixture (99ms test bodies). They cover semantic direction, +ties/repeated originals/self-links, live fallback, unknown types, unambiguous +pair keys, reorder/monotonicity, pre-aggregation coverage and exact conservation +of 50,000 edges across 10,000 items. This is pure projection evidence, not browser +capacity or completed priority-control acceptance. The earlier scaffold could +not import the absent module; no behavioral red-suite pass is implied. + +The draft #224 UI uses a 16px Links control with Essential, Dependencies and All +stops, saved per browser. A primary label such as “Blocks +2” opens every +original relationship for that endpoint pair; unrelated pairs keep their best +available tier before group aggregation. Display changes preserve the camera, +physics inputs and API data. Item and overview counts distinguish summarized +relationships from pending/internal/geometrically deferred records. + +On 2026-10-28, the scoped candidate passed 41 component/helper tests, 10 schematic +routing tests and web types. The two headed priority journeys at 1440/390px passed +in 23.1s: keyboard level changes, persisted choice, exact pair inspection with +focus restoration, unchanged data, and explicit deletion of a blocker revealing +the dependency fallback. Their first run exposed the separate relationship-panel +click propagation bug corrected in #223. Local probes use an owned dependency +fixture. The extended thousands-of-items priority acceptance, exact-source review, +full CI and dev integration remain pending. Actual obstacle-aware item routing +and buses remain issue #220. + + +Explicit incoming/outgoing traces apply the chosen priority tier within that +original direction before paging/routing. An opposite-direction blocker can +remain the global winner without erasing the only incoming dependency. The +trace adds its per-member-pair fallback IDs to the represented external set, +keeps all other partitions exact, and restores the global set on exit. This is +a scoped presentation decision; the persisted Links setting and canonical +relationships do not change. See the dated trace-scope/control-space lesson for +the failed full-CI cases and the screenshot-rejected first phone correction. diff --git a/docs/architecture/topology-organization.md b/docs/architecture/topology-organization.md new file mode 100644 index 00000000..46efa6fe --- /dev/null +++ b/docs/architecture/topology-organization.md @@ -0,0 +1,31 @@ +# Organize by connections and leave room to read + +Owner feedback on2026-10-28: overview remains crowded despite readable labels and working hover; detailed mode remains a junk pile. A dominant hub should occupy the center of its neighbors, with circular arrangements where the graph calls for them. This implementation is tracked by #233, complements detailed routing #220 and does not establish complete LOD acceptance by itself. + +## Spatial contract + +Use actual card sizes. Reserve at least64px between overview columns and64px between rows, and at least20px of routing clearance around unrelated cards. Wire lanes from unrelated buses must have a measurable separation, not just avoid exact intersections. Keep a complete relationship browser and explicit deferred counts; passing by hiding all useful routes is forbidden. Phone layouts keep readable cards and fewer representatives, not scaled-down text. + +## Topology and bounded refinement + +Compute undirected connectivity for placement while retaining every original directed relationship for rendering/inspection. Parallel or redundant types cannot multiply a node's centrality. Split disconnected components. A dominant connected hub occupies the center; its neighbors occupy angular sectors around it. Chains and cycles follow their own ordered path/ring. Deeper networks use bounded radial breadth levels with related branches adjacent, with radii derived from measured dimensions and spacing. Pack components without overlap. Use deterministic IDs/ties and bounded iterative ordering, report work, and avoid all-pairs force work on every animation tick. + +Overview admits readable representatives through its existing hierarchy/parent fallback and full lists, then iteratively improves slot assignment using its actual represented relationships. Preserve a central slot for a clear hub where space permits. Camera gestures and hover retain accepted positions. + +Detailed organization uses the existing explicit organize/save lifecycle. Saved layouts stay authoritative on load. A genuinely unplaced scene can use the topology organizer initially; adding items must not reposition every existing item. The original localhost graph is not a test fixture. + +## Validation and limits + +Keep small deterministic hub, chain, cycle, disconnected, long-card and phone cases; reject a deliberately crowded negative. Inspect real browser screenshots and geometry from the app, including exact connection coverage, card/wire clearance, endpoint identity, edits, saved reload and unchanged camera during inspection. Extend the existing2,000-item/5,000-relationship hosted gate; larger capacity and hosted parity remain separate. + +CodeGraph's circle packing, per-region admission, parent fallback and stable renderer/camera identity inform these choices. GraphDone's rectangular cards, directed task relationships and saved layouts need an independent implementation; no shared library or CodeGraph change. The existing orthogonal overview router is a useful bounded reference. Detailed item routing around arbitrary cards remains #220 and cannot be claimed solved by radial placement alone. + +The existing one-time `separateCards` repair remains an exception for already overlapping saved coordinates: it can move those cards and save the repair on load. This predates the topology organizer. Already-clear saved arrangements remain unchanged until an explicit organization action. The original localhost graph is not a fixture. + +Organization retains every simulation item even when only a viewport subset is painted. It explicitly exits the legacy dependency-row mode, including delayed callbacks. The camera keeps the selected component's hub horizontally centered, first keeps the focused hub clear of controls, then ranks complete visible cards before other control overlap, and uses a readable zoom floor for the central neighborhood. Small phone hubs choose from bounded angle/aspect candidates using measured rectangular clearance, so a five-item system keeps all five cards readable. Larger neighborhoods may require panning; explicit Zoom to fit remains available. Camera framing examines at most32 nearby cards and16 vertical candidates. Save-status transitions are separate from final acknowledged-save screenshot acceptance; transient/error overlays are not claimed solved here. + +Final small-scene visuals require every card and title actually painted, at least12px of screen/control margin, complete short words, full nine/five-card desktop/phone containment, central hub, exact saved positions and settled camera restoration. A correct bounding box during an entrance fade is not visual acceptance. Normal transient save progress is excluded from permanent organized framing; attention/error panels remain obstacles. Explicit organization stops the initial camera-framing loop. + +Compact heterogeneous slots are not interchangeable. Every proposed compact-ring swap revalidates both moved cards against all component rectangles before accepting its connection-length improvement. Rectangle evaluations and rejected swaps are reported, with a separate40,000 evaluation cap. Long-title/nested-card cross-link negatives retain the initial64px clearance after refinement. + +After explicit organization, publish the new complete positions/types through the existing minimap publisher immediately. A pinned one-shot simulation emits too few ticks to rely on periodic tick publication, and finite startup polling may already have ended. Navigation state is derived display state; publishing it must not reframe the camera or restart idle physics. diff --git a/docs/auth-improvements-summary.md b/docs/auth-improvements-summary.md index 53bf0f27..a2d2bf29 100644 --- a/docs/auth-improvements-summary.md +++ b/docs/auth-improvements-summary.md @@ -1,5 +1,9 @@ # Authentication Improvements Summary +The login lockout described below is browser-side behavior, not server-side +password-login throttling. See the [2026-10-23 security scope](quality/SECURITY_SCOPE_2026-10-23.md) +for current API findings, tested boundaries, and remaining work. + ## ✅ Completed Enhancements (2025-01-09) ### 1. **Rate Limiting & Brute Force Protection** ⭐ HIGH PRIORITY diff --git a/docs/deployment.md b/docs/deployment.md index 1add72b7..730674eb 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -7,13 +7,13 @@ GraphDone can be installed with a single command on macOS and Linux: ### Using GitHub (Recommended) ```bash -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh | sh +curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh | sh ``` or using wget: ```bash -wget -qO- https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh | sh +wget -qO- https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh | sh ``` ### Using Custom Domain (Future) @@ -124,13 +124,13 @@ Your GraphDone instance will be available at: ```bash # Stop all GraphDone services -sh ~/graphdone/public/install.sh stop +sh ~/graphdone/scripts/install.sh stop # Complete cleanup (removes containers, volumes) -sh ~/graphdone/public/install.sh remove +sh ~/graphdone/scripts/install.sh remove # Reinstall/update GraphDone -sh ~/graphdone/public/install.sh install +sh ~/graphdone/scripts/install.sh install ``` ## 📊 System Requirements @@ -180,16 +180,16 @@ To host the installation script on graphdone.com: ### 1. Copy Script to Web Server -Place `public/start.sh` in your web server's document root: +Place `scripts/install.sh` in your web server's document root: ```bash # Example for nginx -sudo cp public/start.sh /var/www/graphdone.com/start.sh -sudo chmod 644 /var/www/graphdone.com/start.sh +sudo cp scripts/install.sh /var/www/graphdone.com/install.sh +sudo chmod 644 /var/www/graphdone.com/install.sh # Example for Apache -sudo cp public/start.sh /var/www/html/start.sh -sudo chmod 644 /var/www/html/start.sh +sudo cp scripts/install.sh /var/www/html/install.sh +sudo chmod 644 /var/www/html/install.sh ``` ### 2. Configure Web Server @@ -251,7 +251,7 @@ If using GitHub Pages for graphdone.com: ```bash # Test the script locally -sh public/start.sh +sh scripts/install.sh # Test from remote URL (once deployed) curl -fsSL https://graphdone.com/start.sh | sh @@ -269,6 +269,15 @@ GRAPHDONE_HOME=/opt/graphdone curl -fsSL https://graphdone.com/start.sh | sh GRAPHDONE_NO_START=1 curl -fsSL https://graphdone.com/start.sh | sh ``` +`scripts/install.sh` installs `main`, the branch it is served from. To install +a release tag or another branch, set `GRAPHDONE_REF` for the shell that runs it +(the clone, updates of `~/graphdone` and the setup-script downloads all follow +it). `sh install.sh sources` prints the repository, ref and URLs it will use: + +```bash +curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh | GRAPHDONE_REF=dev sh +``` + ## Updating GraphDone Users can update their installation by running the same command: @@ -297,18 +306,12 @@ rm -rf ~/.graphdone ```bash # Option 1: Review before running -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh | less +curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh | less # Option 2: Download, inspect, then execute -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh -o install.sh +curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh -o install.sh cat install.sh # Review contents sh install.sh - -# Option 3: Verify with checksums (recommended for production) -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh.sha256 -o install.sh.sha256 -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh -o install.sh -sha256sum -c install.sh.sha256 # Verify integrity -sh install.sh ``` ### What the Script Does diff --git a/docs/deployment/README.md b/docs/deployment/README.md index 81350336..21d1bbc0 100644 --- a/docs/deployment/README.md +++ b/docs/deployment/README.md @@ -6,21 +6,56 @@ This guide covers various deployment options for GraphDone, from local developme ### Development (Local) ```bash -./tools/run.sh +./scripts/stack/run.sh ``` ### Development (Docker) ```bash -./tools/run.sh --docker-dev +./scripts/stack/run.sh --docker-dev ``` ### Production (Docker) ```bash -./tools/run.sh --docker +./scripts/stack/run.sh --docker ``` ## Environment Variables +### Required production signing secrets + +With `NODE_ENV=production`, the API refuses to start if `JWT_SECRET` or +`SESSION_SECRET` is missing, blank, or a published GraphDone default/example +value. This applies to direct Node startup and containers. Development and test +startup retain their existing defaults. + +Generate two independent secrets once for each deployment, store them privately, +and retain them across restarts. For example, create a new file outside this +checkout without printing its contents: + +```bash +(umask 077; set -o noclobber; printf 'JWT_SECRET=%s\nSESSION_SECRET=%s\n' \ + "$(openssl rand -hex 32)" "$(openssl rand -hex 32)" > /secure/path/graphdone.env) +``` + +Supply that file to the legacy production Compose variants: + +```bash +docker compose --env-file /secure/path/graphdone.env -f deployment/docker-compose.yml up -d +``` + +The HTTP and registry variants use the same variables. `scripts/self-host.sh` +already creates separate random secrets in its private environment file. For +direct `npm start --workspace=@graphdone/server` or `node dist/index.js`, set both +environment variables, or put them in `.env` in the server process's working +directory; configuration loads before authentication modules initialize. + +Existing production installations using defaults must configure both secrets +before upgrading. Replacing a JWT key invalidates existing bearer tokens; +replacing a session key invalidates signed sessions, so users must sign in again. +The guard rejects known unsafe configuration; it does not measure arbitrary +secret strength. Use a secret manager or cryptographically random values such +as those above. [Startup regression evidence and limits](../quality/PRODUCTION_SECRETS_2026-10-23.md). + ### Server (.env) ```bash # Database @@ -33,8 +68,9 @@ NODE_ENV=production PORT=4000 CORS_ORIGIN=https://yourdomain.com -# Authentication (when implemented) -JWT_SECRET=your-secure-secret +# Authentication +JWT_SECRET= +SESSION_SECRET= AUTH0_DOMAIN=your-domain.auth0.com AUTH0_CLIENT_ID=your-client-id AUTH0_CLIENT_SECRET=your-client-secret @@ -57,6 +93,37 @@ VITE_ENABLE_3D_VIEW=true ## Docker Deployment +### Preserve existing SQLite data before recreating legacy API containers + +The HTTPS, HTTP and development Compose files retain the named volume +`sqlite_auth_data` and mount it at `/app/packages/server/data`, matching the +server workspace. This protects `auth.db`, `operations.db` and any private +`bootstrap-admin.json`. The separate self-host stack already uses this path. + +Older versions mounted that volume at `/app/data` while the API wrote to its +container's `/app/packages/server/data` directory. Before upgrading an existing +installation, identify and stop its API container without removing it, then +copy that entire runtime data directory into a private backup. Stopping the +writer first preserves a consistent SQLite database and any journal files. +Retain the original container and existing named volume until restoration and +login have been verified. Inspect both the backup and existing volume before +choosing the authoritative data; do not blindly overwrite either. Restore the +chosen backup into the same volume at the corrected mount, preserving file +permissions (development uses UID/GID 1001), then recreate and verify the same +account ID and login. Do not use `down -v` for this migration. This change does +not automatically extract data from an old container's writable layer. + +For a new production database, optionally supply `ADMIN_EMAIL` and a private +`ADMIN_PASSWORD` of at least 20 characters through the Compose environment. +Without a password, production still generates one and saves it as a mode-0600 +`bootstrap-admin.json` in the persistent directory. Setting these variables does +not reset an existing administrator's credentials. CI supplies a disposable +password and passes the same value as `TEST_ADMIN_PASSWORD` to the browser +helper; these fixture values must not appear in logs or committed files. + +The [recreation regression and migration limits](../../.agents/inbox/2026-10-23-sqlite-volume-workspace.md) +record the failure and recovery evidence. + ### Production Stack 1. **Build and start services:** @@ -124,11 +191,13 @@ kubectl get ingress graphdone-ingress 1. **Create secrets:** ```bash kubectl create secret generic graphdone-secrets \ - --from-literal=neo4j-uri="bolt://..." \ - --from-literal=neo4j-password="your-password" \ - --from-literal=jwt-secret="your-secret" + --from-env-file=/secure/path/graphdone.env ``` + Use the privately generated file described above. Map its `JWT_SECRET` and + `SESSION_SECRET` keys into the API container environment, for example with + `envFrom.secretRef`. Supply the deployment's Neo4j configuration separately. + 2. **Configure ingress:** ```yaml # k8s/ingress.yaml @@ -441,4 +510,4 @@ docker compose build --no-cache For deployment support: - [GitHub Issues](https://github.com/GraphDone/GraphDone-Core/issues) - [Community Discord](https://discord.gg/graphdone) -- [Documentation](../README.md) \ No newline at end of file +- [Documentation](../README.md) diff --git a/docs/deployment/SELF_HOSTING.md b/docs/deployment/SELF_HOSTING.md new file mode 100644 index 00000000..b3de5ad0 --- /dev/null +++ b/docs/deployment/SELF_HOSTING.md @@ -0,0 +1,21 @@ +# Self-host GraphDone Core + +Core includes the task app, human/agent notes, project-scoped agent connections, feedback inbox, administrator review and scoped operator access. No subscription is required for these capabilities. The hosted product sells managed operation and execution resources. See [product strategy](../product/CORE_HOSTED_STRATEGY.md). + +From a checkout, with Docker Compose, Git and OpenSSL available: + +```sh +./scripts/self-host.sh your-domain.example +``` + +Point your domain at the VPS and make ports 80/443 reachable. Caddy handles HTTPS. For a local installation use `./scripts/self-host.sh` and open http://localhost:8080. Existing configuration is reused on subsequent runs. `GRAPHDONE_SETUP_ONLY=1 ./scripts/self-host.sh` generates configuration without starting containers. + +Sign in as **admin**. Retrieve `ADMIN_PASSWORD` from `deployment/self-host/.env`, a mode-600 file created on your machine. Back it up privately. The installer generates separate database, JWT, session and admin secrets. It does not use the demo credentials. Database/API ports are private, containers are not privileged, and the API runs as a non-root user. Source build contexts exclude environment files, runtime databases, certificates, keys, logs and node_modules. + +The API uses the `auth_data` volume for both authentication and `operations.db`; Neo4j uses `graph_data`. Caddy has separate certificate/configuration volumes. `docker compose --env-file deployment/self-host/.env -f deployment/self-host/compose.yml down` stops services without deleting data. Do not add `-v` unless you intend to delete the installation's volumes. Back up the private environment file and all persistent volumes, and test restoration on a separate installation before depending on a backup. + +New production installations outside this Compose setup can provide `ADMIN_EMAIL` and a strong `ADMIN_PASSWORD` (minimum 20 characters). Without a supplied password, the first-start bootstrap generates a random one and stores it in the persistent `data/bootstrap-admin.json` with mode 600. It does not create the development viewer. Existing accounts are preserved; upgrading does not silently change their passwords. Operators of older demo deployments must rotate the known demo credentials before exposing them publicly. + +The old registry/development Compose files are legacy development paths; this is the recommended public-hosting path. This repository is not yet a published Hostinger one-click marketplace package. Build and startup validation results are recorded in the release QC ledger; runtime capacity depends on the machine and workload. The initial Neo4j heap/page-cache settings are explicit in `compose.yml` so operators can measure and adjust them. + +SMTP/SSO and outbound model credentials are separate optional configuration. Never put provider keys into Vite variables. Core's feedback and operator APIs run on local SQLite and do not call graphdone.com or consume its D1 allowance. Optional AI runners use only the gateway you configure. The current Neo4j Core still has offline-sync/transfer parity work compared with the managed D1 build; this is tracked engineering debt, not a paid restriction. diff --git a/DEVOPS_INTEGRATION.md b/docs/deployment/devops-integration.md similarity index 97% rename from DEVOPS_INTEGRATION.md rename to docs/deployment/devops-integration.md index 374c4371..bbaa0704 100644 --- a/DEVOPS_INTEGRATION.md +++ b/docs/deployment/devops-integration.md @@ -219,7 +219,7 @@ fi ```bash # After test run completes -./tools/generate-test-manifest.sh test-reports artifacts-20251113_220000 +./scripts/vm/generate-test-manifest.sh test-reports artifacts-20251113_220000 # Output: test-reports/artifacts-20251113_220000/test-manifest.json ``` @@ -273,10 +273,10 @@ jobs: - uses: actions/checkout@v3 - name: Run E2E Tests - run: ./tools/test-vm-e2e.sh + run: ./scripts/vm/test-vm-e2e.sh - name: Generate Manifest - run: ./tools/generate-test-manifest.sh + run: ./scripts/vm/generate-test-manifest.sh - name: Upload Artifacts uses: actions/upload-artifact@v3 diff --git a/docs/design/human-ai-workspace.md b/docs/design/human-ai-workspace.md new file mode 100644 index 00000000..e6a7a9eb --- /dev/null +++ b/docs/design/human-ai-workspace.md @@ -0,0 +1,45 @@ +# Human-first workspace, observable AI work + +Product direction clarified 2026-09-07: GraphDone is a friendly place for people to work and understand AI-driven development. Agents participate through clear, authenticated interfaces. Human control and understandable progress drive the interface; infrastructure and exhaustive controls stay secondary. + +## Product boundary and current priority + +Core remains the free, open-source foundation. A managed GraphDone web product can add advanced autonomous execution, LiteLLM gateway integration and external ChatGPT/OpenAI agent interfaces. Do not imply these services ship in Core today. + +The current implementation priority is visual/UI/UX correctness across common resolutions and orientations. Minimize both button presses per task and the number of tasks required to reach an outcome. Primary work should be directly actionable; avoid intermediate read-only dialogs or unnecessary save/confirmation steps. Secondary configuration can expand on demand. Gateway and autonomous-execution implementation follows this UI work. + +## Human questions the default experience must answer + +- What is happening? Show active work, who or which agent is working on it, and the latest meaningful update. +- What needs me? Surface blockers, questions, review requests and decisions with a clear next action. +- What changed? Show progress and evidence since the person's last visit, with links to the work, tests, artifacts and discussions. + +Keep the graph as a powerful exploration view. A phone should offer the same essential actions in a readable list and compact editor. Details, filters, history and configuration expand on demand. Never show fabricated agent activity, simulated progress, or an action that appears to succeed without persistence. + +## Shared human/agent work contract + +Use one authorized domain API behind UI and agent tools. Minimum operations: + +1. Read a bounded work context: graph, requirements, dependencies, eligible work, current revision and caller permissions. +2. Claim eligible work atomically with actor identity, lease expiry and expected revision; reject competing claims. Humans can release/reassign within their permissions. +3. Append a progress event: actor, work item, status, plain-language summary, timestamp, evidence links and idempotency key. Retries must not duplicate history. +4. Report a blocker or ask a question; place it in the human's attention queue with a suggested next action. +5. Submit work for review with evidence and acceptance-check results; completion policy is explicit per graph. Do not equate an agent saying “done” with verified requirements. +6. Resume after reconnect using a durable event cursor/revision, including deletions and failed/abandoned claims. + +Suggested vocabulary: `get_work_context`, `claim_work`, `report_progress`, `request_input`, `submit_for_review`, `release_work`. These names describe the desired contract, not currently shipped tools. Tools must be available over a hosted interface and use scoped credentials; local database access alone does not meet the online requirement. + +## Observability without overload + +A current-work summary points to a durable event history. Preserve who did what and why, distinguish reported from verified progress, and provide evidence without exposing secrets or dumping internal reasoning. Live transport can broadcast changes, but reconnects recover from durable storage. Cursor/presence updates are ephemeral; task status, blockers, review decisions and evidence are persistent. + +## Acceptance scenarios + +- A human creates a requirement and two tasks; an authorized agent finds and claims eligible work, reports an update, and submits evidence for review. The human sees the changes without reloading. +- Two agents try to claim the same task; exactly one succeeds, and the other receives an actionable conflict. +- An agent requests input; the human answers from a phone in portrait, and the agent can resume. +- A network retry repeats a progress report without duplicating history; reconnect restores missed updates. +- A revoked or cross-graph credential cannot read, claim or update work. +- At 320px portrait and short landscape, primary actions and the current result remain visible; controls, charts and metadata never displace the actual work. Keyboard navigation, reduced motion and assistive labels work. + +These are release requirements. Existing CRUD, status notes, MCP and polling are useful foundations but do not by themselves satisfy this contract. diff --git a/docs/design/progressive-streaming.md b/docs/design/progressive-streaming.md index 03417af7..3da58982 100644 --- a/docs/design/progressive-streaming.md +++ b/docs/design/progressive-streaming.md @@ -3,6 +3,13 @@ > Status: design accepted (2026-06-11). Implementation next. > Story: ADAPT-4 in [USER_STORIES.md](../USER_STORIES.md). Budget: TTFP < 2s on simulated 3G for a 1k-node graph. +> **2026-10-27 review:** implementation remains pending. The current merge helpers +> consume complete snapshots, remove absent nodes and drop edges with unloaded +> endpoints; passing each page directly to them is unsafe. Follow the +> [dynamic LOD continuation](../architecture/dynamic-graph-lod.md) for the revised +> source-registry, summary-first loading, revision/cursor and completeness +> contracts. The mechanics below are historical design, not shipped behavior. + ## Problem The graph view fetches **every** work item and edge in the graph in one query. At 1k+ nodes on a slow link this blows the time-to-first-paint budget, and most of those nodes land outside the initial viewport anyway. Quality tiers already bound how much we *render* (`maxInitialNodes`); nothing bounds what we *fetch*. diff --git a/docs/detailed-overview.md b/docs/detailed-overview.md index 712d1b57..2916a5b5 100644 --- a/docs/detailed-overview.md +++ b/docs/detailed-overview.md @@ -2,11 +2,20 @@ > This document contains the comprehensive technical documentation, architecture details, and implementation guides for GraphDone. +> ⚠️ **Accuracy note:** the actual stack is **React + Vite + Tailwind + D3** (web), +> **Node + Apollo + @neo4j/graphql** (server), **Neo4j** (graph) + **SQLite** (auth), +> and an **MCP** server; the hosted edition runs on **Cloudflare Workers + D1** (see +> the GraphDone-Cloud repo). Some sections below describe earlier or aspirational +> designs (e.g. PostgreSQL, Prisma, Redis, React Native) that are **not** the current +> implementation. For the concise current picture see +> [guides/architecture-overview.md](./guides/architecture-overview.md) and +> [guides/web-ui-architecture.md](./guides/web-ui-architecture.md). + ## How GraphDone Works: A Visual Deep Dive ### The Core Concept: Work as a Graph -Traditional project management tools organize work in linear lists or hierarchical trees. GraphDone models work as it actually exists - a network of interconnected outcomes, dependencies, and relationships. +Traditional management tools organize work in linear lists or hierarchical trees. GraphDone models work as it actually exists - a network of interconnected outcomes, dependencies, and relationships. ```mermaid graph TD @@ -480,7 +489,7 @@ GraphDone includes a comprehensive setup script that handles all prerequisites a # Clone and setup git clone https://github.com/GraphDone/GraphDone-Core.git cd graphdone -./tools/setup.sh +./scripts/stack/setup.sh ``` The setup script will: @@ -495,19 +504,19 @@ The setup script will: ```mermaid graph LR - START[./tools/setup.sh] --> DEV[./tools/run.sh] - DEV --> TEST[./tools/test.sh] - TEST --> BUILD[./tools/build.sh] - BUILD --> DEPLOY[./tools/deploy.sh] + START[./scripts/stack/setup.sh] --> DEV[./scripts/stack/run.sh] + DEV --> TEST[./scripts/stack/test.sh] + TEST --> BUILD[./scripts/stack/build.sh] + BUILD --> DEPLOY[./scripts/stack/deploy.sh] DEV -.-> CODE[Code Changes] CODE -.-> DEV subgraph "Available Commands" - RUN1[./tools/run.sh --docker-dev] - RUN2[./tools/test.sh --coverage] - RUN3[./tools/test.sh --package core] - BUILD1[./tools/build.sh --production] + RUN1[./scripts/stack/run.sh --docker-dev] + RUN2[./scripts/stack/test.sh --coverage] + RUN3[./scripts/stack/test.sh --package core] + BUILD1[./scripts/stack/build.sh --production] end ``` @@ -515,7 +524,7 @@ graph LR Start all development servers: ```bash -./tools/run.sh +./scripts/stack/run.sh ``` This provides: @@ -550,10 +559,10 @@ For containerized development: ```bash # Full Docker development environment -./tools/run.sh --docker-dev +./scripts/stack/run.sh --docker-dev # Production-like environment -./tools/run.sh --docker +./scripts/stack/run.sh --docker # Individual service testing docker compose up -d postgres # Database only @@ -666,8 +675,8 @@ agent.subscribe('node.priorityChanged', async (node) => { # Get started in 30 seconds git clone https://github.com/GraphDone/GraphDone-Core.git cd GraphDone-Core -./tools/setup.sh -./tools/run.sh +./scripts/stack/setup.sh +./scripts/stack/run.sh ``` Visit http://localhost:3000 to see the working application! @@ -711,16 +720,16 @@ GraphDone is built for and by teams who think differently. We welcome contributi ### 🔧 **Development Workflow** ```bash # Set up development environment -./tools/setup.sh +./scripts/stack/setup.sh # Make your changes git checkout -b feature/your-improvement # Test your changes -./tools/test.sh --coverage +./scripts/stack/test.sh --coverage # Build and verify -./tools/build.sh +./scripts/stack/build.sh # Submit your contribution git push origin feature/your-improvement @@ -732,10 +741,7 @@ git push origin feature/your-improvement ### Self-Hosted ```bash # Using Docker Compose -docker compose -f docker-compose.prod.yml up -d - -# Using Kubernetes -kubectl apply -f tools/deployment/k8s/ +docker compose -f deployment/docker-compose.yml up -d ``` ### Cloud Hosting diff --git a/docs/error-handling-improvements.md b/docs/error-handling-improvements.md index a049ec1a..033c1276 100644 --- a/docs/error-handling-improvements.md +++ b/docs/error-handling-improvements.md @@ -58,7 +58,7 @@ The error handler now recognizes and provides specific guidance for: - Added `safe_docker()` wrapper for Docker commands - Enhanced `cmd_stop()` with better error handling -#### `tools/run.sh` Script +#### `scripts/stack/run.sh` Script - Removed `set -e` to allow graceful error handling - Added `handle_docker_error()` function - Wrapped critical docker-compose commands with error detection @@ -161,7 +161,7 @@ Potential improvements for future versions: To add detection for a new error pattern: -1. Update `handle_docker_error()` in both `start` and `tools/run.sh` +1. Update `handle_docker_error()` in both `start` and `scripts/stack/run.sh` 2. Add a new `elif` clause with the error pattern 3. Provide clear issue description and solution steps 4. Update `docs/troubleshooting-docker.md` diff --git a/docs/formats/PROJECT_TRANSFER.md b/docs/formats/PROJECT_TRANSFER.md new file mode 100644 index 00000000..1303f5ac --- /dev/null +++ b/docs/formats/PROJECT_TRANSFER.md @@ -0,0 +1,17 @@ +# Project JSON and Mermaid + +On the hosted/offline-enabled app, open the project selector in graph view. **Export JSON** downloads just the selected project, including unsynced local changes. **Import project** accepts project JSON or existing GraphDone offline backups. Settings → Import backup accepts both too. Imports are atomic local saves with new project, task, connection and note IDs; the original remains untouched. Select the imported copy in the project list. Guest copies remain local; signed-in copies upload when allowance is available. + +`graphdone-project`, version `1`, is GraphDone's documented interchange format, not a claim of compatibility with every graph application. It uses standard JSON and JSON Schema 2020-12. The public schema is `/schemas/project-v1.schema.json`; [example](project-v1.example.json). Arrays are `nodes`, `edges`, and `notes`; project properties are under `project`. Task tags are a JSON array; `metadata` remains a string to preserve existing custom payloads exactly. Priority is 0–1. Positions are canvas coordinates. IDs must be unique per array and connection/note task references must exist. Status/type names are preserved. JSON Schema describes shape; the importer also validates references and uniqueness. + +Export contains no account owner IDs, agent credentials, authentication, permission grants, or sync receipts. Descriptions and notes are included: review them before sharing. Displayed note author labels are preserved locally, but cloud sync records imported notes under the importing human's authority. Notes whose original task was deleted are exported as project notes, preserving their text. Project IDs are informational: importing always makes copies. Account settings, tokens and sharing permissions do not transfer. Pending sync conflicts should be resolved before transferring: individual JSON exports the current local version; full offline backup retains both versions. + +Limits: 50 MB per file, 100 projects total after import, 300 tasks / 600 connections / 2,000 notes per project. Invalid files and unsupported versions fail without partial imports. Unrecognized fields do not receive authority and are omitted by the project importer. Offline backups remain compatible. + +**Export Mermaid** downloads `.mmd` source using [Mermaid flowchart syntax](https://mermaid.js.org/syntax/flowchart.html). It includes task titles, statuses and directed connection types; labels are escaped as entities and IDs are generated independently of user text. Disconnected tasks and cyclic connections are supported. The renderer chooses layout. Descriptions, notes, priorities, dates and exact canvas positions are available in JSON, not this diagram. Mermaid import is not supported: Mermaid is a presentation export, not a lossless backup. + +## Repeatable validation + +- `npm run test -w @graphdone/web -- src/lib/project-transfer src/lib/offline/model.test.ts` +- Build offline-enabled web against a local API, then run headed, serial: `DISPLAY=:1 PLAYWRIGHT_BROWSERS_PATH=/tmp/graphdone-playwright TEST_URL=http://localhost:3136 npx playwright test tests/e2e/quality/project-transfer.spec.ts --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --reporter=line` +- The guest transfer suite makes no cloud writes. It imports the documented example through the file picker, exports and reimports it, checks fresh IDs/references/content and reload, verifies invalid import atomicity, and parses/renders Mermaid with the real dev-only Mermaid package. Responsive controls are checked at phone portrait/landscape, tablet and desktop sizes. diff --git a/docs/formats/project-v1.example.json b/docs/formats/project-v1.example.json new file mode 100644 index 00000000..15323154 --- /dev/null +++ b/docs/formats/project-v1.example.json @@ -0,0 +1,58 @@ +{ + "format": "graphdone-project", + "version": 1, + "project": { + "id": "science-project", + "name": "Student launch project" + }, + "nodes": [ + { + "id": "plan", + "title": "Plan the launch 🚀", + "type": "TASK", + "description": "Check weather and safety", + "status": "IN_PROGRESS", + "priority": 0.8, + "position_x": -120, + "position_y": 40, + "due_date": "2026-10-01", + "tags": [ + "science", + "team" + ], + "metadata": "{\"custom\":true}" + }, + { + "id": "launch", + "title": "Launch", + "type": "TASK", + "description": null, + "status": "PROPOSED", + "priority": 0.5, + "position_x": 200, + "position_y": 80, + "due_date": null, + "tags": [], + "metadata": "{}" + } + ], + "edges": [ + { + "id": "dependency", + "source_id": "plan", + "target_id": "launch", + "type": "DEPENDS_ON", + "weight": 1 + } + ], + "notes": [ + { + "id": "handoff", + "work_item_id": "plan", + "body": "Ask the agent to check the forecast.", + "actor": "Student team", + "actor_type": "HUMAN", + "created_at": "2026-09-08T00:00:00Z" + } + ] +} diff --git a/docs/guides/architecture-overview.md b/docs/guides/architecture-overview.md index 1bf6f774..347c0328 100644 --- a/docs/guides/architecture-overview.md +++ b/docs/guides/architecture-overview.md @@ -8,13 +8,17 @@ GraphDone is architected around three core principles: 2. **Real-Time First**: Changes propagate immediately to all participants 3. **Democratic Coordination**: Priority emerges from community validation, not top-down assignment +> **Client / UI layer:** for the web app's structure (view system, graph canvas, +> mobile shell, node inspector) see [web-ui-architecture.md](./web-ui-architecture.md). +> Some sections below describe the forward-looking server/infra target, not all of +> which is wired up today. + ## Current Architecture (v0.3.1-alpha) ```mermaid graph TB subgraph "Client Applications" - WEB[Web Application<br/>React + TypeScript + D3.js<br/>Touch-optimized UI<br/>Port 3127] - IOS[iPhone App<br/>SwiftUI<br/>Separate GraphDone-iOS repo] + WEB[Web Application<br/>React + TypeScript + D3.js<br/>Responsive desktop + mobile web<br/>Port 3127] CLAUDE[Claude Code MCP<br/>Natural language interface<br/>Port 3128] end @@ -25,7 +29,7 @@ graph TB subgraph "Data Layer" SQLITE[(SQLite<br/>User authentication<br/>User settings & preferences<br/>Fast local storage)] - NEO4J[(Neo4j 5.15-community<br/>Project management graph<br/>Work items & dependencies<br/>APOC plugins<br/>Port 7687)] + NEO4J[(Neo4j 5.15-community<br/>Management graph<br/>Work items & dependencies<br/>APOC plugins<br/>Port 7687)] REDIS[(Redis 8-alpine<br/>Available in Docker<br/>Not yet integrated<br/>Port 6379)] end @@ -36,7 +40,6 @@ graph TB end WEB --> GQL - IOS --> GQL CLAUDE --> NEO4J GQL --> SQLITE GQL --> NEO4J @@ -231,7 +234,7 @@ GraphDone uses a hybrid database approach, optimizing each database for its spec - No network latency for auth operations **Neo4j: Graph Data** -- Project management nodes (tasks, outcomes, milestones) +- Management nodes (tasks, outcomes, milestones) - Dependencies and relationships between work items - Graph traversal and pathfinding operations - Complex graph queries and analytics diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index 8a13d467..45e8647d 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -1,6 +1,6 @@ # Getting Started with GraphDone -Welcome to GraphDone! This guide will help you set up and start using GraphDone for your team's project management needs. +Welcome to GraphDone! This guide will help you set up and start using GraphDone for your team's management needs. ## Prerequisites @@ -24,7 +24,7 @@ cd GraphDone-Core ./start # OR run setup script directly -./tools/setup.sh +./scripts/stack/setup.sh ``` The setup script will: @@ -64,7 +64,7 @@ Start the development servers: ./start # OR start servers directly -./tools/run.sh +./scripts/stack/run.sh ``` This will start: diff --git a/docs/git-hooks.md b/docs/guides/git-hooks.md similarity index 100% rename from docs/git-hooks.md rename to docs/guides/git-hooks.md diff --git a/docs/guides/user-flows.md b/docs/guides/user-flows.md index fbb0ef61..d8b4dfb3 100644 --- a/docs/guides/user-flows.md +++ b/docs/guides/user-flows.md @@ -4,7 +4,7 @@ ### 1. New User Onboarding Flow -Understanding how users first experience GraphDone's unique approach to project management. +Understanding how users first experience GraphDone's unique approach to management. ```mermaid journey @@ -391,4 +391,4 @@ graph LR style L3 fill:#f3e5f5 ``` -These user flows demonstrate how GraphDone's unique approach to project management creates natural, intuitive workflows that scale from individual contributors to large organizations while maintaining the core principles of democratic coordination and human-AI collaboration. \ No newline at end of file +These user flows demonstrate how GraphDone's unique approach to management creates natural, intuitive workflows that scale from individual contributors to large organizations while maintaining the core principles of democratic coordination and human-AI collaboration. \ No newline at end of file diff --git a/SETUP_MULTIPASS.md b/docs/guides/vm-setup.md similarity index 96% rename from SETUP_MULTIPASS.md rename to docs/guides/vm-setup.md index 4287fc08..57c3ceb7 100644 --- a/SETUP_MULTIPASS.md +++ b/docs/guides/vm-setup.md @@ -89,7 +89,7 @@ sudo snap install multipass To run comprehensive E2E tests in a VM: ```bash -./tools/test-vm-e2e.sh <branch-name> +./scripts/vm/test-vm-e2e.sh <branch-name> ``` This will: @@ -103,7 +103,7 @@ This will: ### Configuration -Edit `vm.config.yml` to change default settings: +Edit `scripts/vm/vm.config.yml` to change default settings: ```yaml vm: @@ -208,7 +208,7 @@ multipass exec <vm-name> -- sudo tailscale up \ ### Disabling Tailscale -If you don't need Tailscale, disable it in `vm.config.yml`: +If you don't need Tailscale, disable it in `scripts/vm/vm.config.yml`: ```yaml tailscale: @@ -276,12 +276,12 @@ This section outlines strategies to speed up E2E testing with Multipass VMs. **Implementation:** ```bash # Create base images for common branches -./tools/create-base-image.sh main -./tools/create-base-image.sh develop -./tools/create-base-image.sh vm_multi-pass +./scripts/vm/create-base-image.sh main +./scripts/vm/create-base-image.sh develop +./scripts/vm/create-base-image.sh vm_multi-pass # Use cached image for testing -./tools/test-vm-e2e.sh main --use-cache +./scripts/vm/test-vm-e2e.sh main --use-cache ``` **Cache Invalidation:** diff --git a/docs/guides/web-ui-architecture.md b/docs/guides/web-ui-architecture.md new file mode 100644 index 00000000..b8fc7c2a --- /dev/null +++ b/docs/guides/web-ui-architecture.md @@ -0,0 +1,62 @@ +# Web / UI Architecture + +How the client (`packages/web`, React 18 + Vite + Tailwind + D3) is structured. +This is the companion to [architecture-overview.md](./architecture-overview.md), +which covers the server / graph engine / data layer. + +```mermaid +graph TD + App["App.tsx<br/>GraphProvider + ViewModeProvider"] + App --> Layout["Layout<br/>sidebar · header · MobileBottomNav"] + Layout --> WS["Workspace<br/>graph selector · data queries"] + WS --> VM["ViewManager<br/>renders the active view mode"] + WS --> Insp["NodeInspector (docked)<br/>+ on-canvas expand peek"] + + VM -->|list / cards| Card["CardView"] + VM -->|graph| SGV["SafeGraphVisualization<br/>→ InteractiveGraphVisualization (D3)"] + VM -->|table · kanban · gantt<br/>calendar · dashboard · activity| Other["other views"] + + VMC["ViewModeContext<br/>active mode + persistence"] -.-> VM + VMC -.-> Layout + Insp --> Modes["Card · Contents (lazy markdown) · Diagram (sub-graph)"] + Audit["mobile-audit tests (CI)<br/>layout · contrast · dialogs"] -.->|gate every screen| VM +``` + +## Pieces + +- **App shell** — `App.tsx` wraps the tree in `GraphProvider` (current graph + + drill-in/ascend) and **`ViewModeContext`** (`contexts/ViewModeContext.tsx`), the + single source of truth for the active view, persisted to `localStorage`. `Layout` + draws the chrome. +- **View system** — `ViewManager` renders one of 8 modes: `cards`, `graph`, + `table`, `kanban`, `gantt`, `calendar`, `dashboard`, `activity`. Phones default to + **`cards`** (a readable list); desktop defaults to `graph`. +- **Graph** — `SafeGraphVisualization` error-boundary-wraps + `InteractiveGraphVisualization`, the D3 force-directed canvas (one-shot physics, + viewport culling, LOD by zoom — see `LOD_THRESHOLDS`). +- **Node inspector** — a docked `NodeInspector` plus an on-canvas **expand-in-place** + peek, each with a **Card / Contents / Diagram** toggle readable at any zoom + (`NodeContentRenderer` lazy-loads markdown/Prism; `NodeSubgraphPreview` draws a + capped static sub-graph). In-canvas card titles have a zoom-decoupled + **legibility floor**. + +## Responsive tiers (boundary: Tailwind `md`, 768px) + +- **Phone (`<md`)** — `MobileBottomNav` (List / Graph / More) is the primary nav; + slim chrome; the sidebar/desktop header are hidden. +- **Tablet & desktop (`≥md`)** — sidebar rail + full top view-strip + all filters. + +## Quality gate + +`tests/e2e/mobile-audit.spec.ts` + `mobile-dialogs.spec.ts` run the +`tests/lib/mobileAudit.ts` auditors (sideways-scroll, squeezed labels, +low-contrast/invisible text, modals clipped under the nav) across **every** screen +at phone width, in CI right after the smoke gate. + +**Massive local report** — `npm run report:matrix` (then `npm run report:matrix:open`) +runs `tests/e2e/feature-matrix.spec.ts`: every view (8) + page (6) + signin + key +features (inspector / expand peek / create modal) at **6 resolutions** (360→1920), +each cell screenshotted **and** layout/contrast-audited. `tests/generate-matrix-report.mjs` +stitches the screenshots into one self-contained gallery +(`test-artifacts/matrix/index.html`, feature × resolution grid). Heavy; local-only, +not in the smoke gate. diff --git a/docs/img/hero/graphdone-dark.svg b/docs/img/hero/graphdone-dark.svg new file mode 100644 index 00000000..2da87560 --- /dev/null +++ b/docs/img/hero/graphdone-dark.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 440" width="1200" height="440" role="img" aria-label="GraphDone: Relationship management for agents. People and AI agents coordinate through dependencies and outcomes."><title>GraphDone: Relationship management for agents. People and AI agents coordinate through dependencies and outcomes.GraphDone's current work as a live topology: work items from its open issues ride an orbit round their outcome, drawn as GraphDone's logo, each a node in its type's colour with an illustrative owner (a person or an AI agent) and named by a HUD callout with its issue number, over a perspective grid and drifting layers of distant graph. The items complete in dependency order: each node fills, its dependency arcs and its neon spoke to the outcome light and data pulses flow inward, and one of the logo's work nodes comes on; when every item is done the outcome locks and the readout reads 6/6 done. diff --git a/docs/img/hero/graphdone-light.svg b/docs/img/hero/graphdone-light.svg new file mode 100644 index 00000000..c7c5b1a3 --- /dev/null +++ b/docs/img/hero/graphdone-light.svg @@ -0,0 +1 @@ +GraphDone: Relationship management for agents. People and AI agents coordinate through dependencies and outcomes.GraphDone's current work as a live topology: work items from its open issues ride an orbit round their outcome, drawn as GraphDone's logo, each a node in its type's colour with an illustrative owner (a person or an AI agent) and named by a HUD callout with its issue number, over a perspective grid and drifting layers of distant graph. The items complete in dependency order: each node fills, its dependency arcs and its neon spoke to the outcome light and data pulses flow inward, and one of the logo's work nodes comes on; when every item is done the outcome locks and the readout reads 6/6 done. diff --git a/docs/installation-flow.md b/docs/installation-flow.md index 67ffb84e..aeaae5ab 100644 --- a/docs/installation-flow.md +++ b/docs/installation-flow.md @@ -256,7 +256,7 @@ graph TB subgraph GitHub ["GitHub Ecosystem"] Repo[GraphDone-Core Repository] - Script[public/start.sh] + Script[scripts/install.sh] Registry[ghcr.io Registry] end @@ -490,18 +490,12 @@ gantt ```bash # Option 1: Review before running (recommended) -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh | less +curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh | less # Option 2: Download, inspect, then execute -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh -o install.sh +curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/scripts/install.sh -o install.sh cat install.sh sh install.sh - -# Option 3: Verify with checksums (production environments) -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh.sha256 -o install.sh.sha256 -curl -fsSL https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public/install.sh -o install.sh -sha256sum -c install.sh.sha256 -sh install.sh ``` ### What the Script Does diff --git a/docs/philosophy.md b/docs/philosophy.md index e3444dfa..247e3d68 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -4,7 +4,7 @@ ## The Problem We're Solving -Traditional project management tools assume people work best when told what to do, when to do it, and how to do it. They optimize for management visibility and control rather than human autonomy and natural work patterns. This creates friction for high-quality individual contributors who thrive on intrinsic motivation, creative problem-solving, and peer collaboration. +Traditional management tools assume people work best when told what to do, when to do it, and how to do it. They optimize for management visibility and control rather than human autonomy and natural work patterns. This creates friction for high-quality individual contributors who thrive on intrinsic motivation, creative problem-solving, and peer collaboration. These tools particularly fail people who think differently - those with pathological demand avoidance, autism, ADHD, or anyone who experiences traditional hierarchical structures as oppressive rather than helpful. The result is talented individuals either struggling within systems designed against their natural patterns, or being excluded entirely from collaborative work. @@ -141,8 +141,8 @@ We envision a world where: - **Teams self-organize** around shared outcomes - **Technology amplifies** human agency rather than replacing it -GraphDone is our contribution to this future - a tool that respects how people actually think, work, and collaborate while providing the coordination benefits of modern project management. +GraphDone is our contribution to this future - a tool that respects how people actually think, work, and collaborate while providing the coordination benefits of modern management. From todo lists to todone lists. From directives to explorations. From control to connection. -This is project management for people who think differently, built by people who think differently, in service of a more inclusive and effective future of work. +This is management for people who think differently, built by people who think differently, in service of a more inclusive and effective future of work. diff --git a/docs/pr-testing-guide.md b/docs/pr-testing-guide.md deleted file mode 100644 index fd8d5290..00000000 --- a/docs/pr-testing-guide.md +++ /dev/null @@ -1,394 +0,0 @@ -# PR Testing Guide - -## Overview - -GraphDone has a comprehensive test suite designed to validate critical functionality before merging pull requests. This guide explains the PR testing process, available test commands, and how to interpret results. - ---- - -## Quick Start - -### Run PR Tests Locally - -```bash -# Ensure services are running first -./start deploy - -# Run critical PR tests (6 test suites, ~2-3 minutes) -npm run test:pr - -# View HTML report -npm run test:report -open test-results/reports/pr-report.html -``` - -### Run All Tests (Comprehensive) - -```bash -# Run comprehensive test suite (11 test suites, ~5-7 minutes) -npm run test:comprehensive - -# View HTML report -open test-results/reports/index.html -``` - ---- - -## Test Suites Overview - -### PR Critical Tests (`npm run test:pr`) - -These tests MUST pass before merging any pull request. They validate essential functionality: - -| Priority | Test Suite | Description | Typical Duration | -|----------|------------|-------------|------------------| -| 0 | **Installation Script Validation** | Validates `./start` script error handling and Docker operations | ~30s | -| 1 | **TLS/SSL Integration** | Verifies HTTPS certificates, secure connections, protocol handling | ~45s | -| 2 | **Authentication System** | Tests login, logout, session management, security | ~60s | -| 3 | **OAuth LinkedIn Integration** | Validates LinkedIn OIDC flow, profile extraction, token handling | ~40s | -| 4 | **Docker Error Handling** | Tests graceful error detection and user-friendly messaging | ~20s | -| 5 | **Database Connectivity** | Verifies Neo4j connection, query execution, data persistence | ~30s | - -**Total Tests**: ~15-20 critical validations -**Expected Duration**: 2-3 minutes -**Failure Tolerance**: 0 (all tests must pass) - -### Comprehensive Tests (`npm run test:comprehensive`) - -Includes all PR critical tests PLUS additional validation: - -- **UI Basic Functionality** - Button clicks, form inputs, navigation -- **Workspace Scrolling** - Viewport behavior, scrolling interactions -- **Graph Operations** - Node creation, edge manipulation, graph algorithms -- **Real-time Updates** - WebSocket subscriptions, live data sync -- **Comprehensive Interactions** - Complex user workflows, multi-step operations - -**Total Tests**: ~40-50 validations -**Expected Duration**: 5-7 minutes -**Use Case**: Pre-release validation, major feature testing - ---- - -## Test Reports - -### HTML Reports - -Beautiful, interactive reports with: -- ✅ **Summary Cards** - Pass/fail counts, duration, environment info -- 📊 **Expandable Sections** - Click suite headers to view details -- ❌ **Error Details** - Full stack traces for failed tests -- 🌐 **Browser Compatibility Matrix** - Cross-browser validation status -- 🎨 **GraphDone Branding** - Professional visual design - -**Report Locations**: -- PR tests: `test-results/reports/pr-report.html` -- Comprehensive tests: `test-results/reports/index.html` - -### JSON Reports - -Machine-readable output for CI/CD integration: -- PR tests: `test-results/reports/pr-results.json` -- Comprehensive tests: `test-results/reports/results.json` - -**JSON Structure**: -```json -{ - "timestamp": "2025-11-12T19:30:00.000Z", - "environment": "production", - "baseUrl": "https://localhost:3128", - "totalTests": 18, - "passed": 15, - "failed": 3, - "skipped": 0, - "duration": 145000, - "suites": [...] -} -``` - ---- - -## CI/CD Integration - -### GitHub Actions Workflow - -PR tests run automatically on every pull request: - -**Workflow Steps**: -1. Checkout code -2. Install dependencies -3. Install Playwright browsers -4. Generate development certificates -5. Start GraphDone services (Docker) -6. Run `npm run test:pr` -7. Upload test results as artifacts -8. Upload HTML report - -**Viewing CI Test Results**: -1. Go to PR → "Checks" tab -2. Find "PR Critical Tests" job -3. Click "Details" to view logs -4. Download artifacts to view HTML report - -**CI Failure Handling**: -- PR cannot be merged if PR validation fails -- All other jobs (lint, typecheck, security) must also pass -- Review test logs and fix issues before re-requesting review - ---- - -## Writing New Tests - -### Adding Tests to PR Suite - -Critical tests should be added to `tests/run-pr-tests.js`: - -```javascript -const PR_TEST_SUITES = [ - { - name: 'My New Critical Test', - command: 'npx playwright test tests/e2e/my-test.spec.ts', - priority: 6, // Add after existing tests - critical: true - } -]; -``` - -### Test Structure - -Follow the existing pattern in `tests/e2e/`: - -```typescript -import { test, expect } from '@playwright/test'; -import { login, navigateToWorkspace, TEST_USERS } from '../helpers/auth'; - -test.describe('My Feature', () => { - test('should do something critical', async ({ page }) => { - // Use auth helper for consistent login - await login(page, TEST_USERS.ADMIN); - await navigateToWorkspace(page); - - // Your test logic here - const element = page.locator('[data-testid="my-element"]'); - await expect(element).toBeVisible(); - }); -}); -``` - -### Shell Script Tests - -For testing bash scripts and Docker operations: - -```bash -#!/bin/bash -# tests/my-shell-test.sh - -TOTAL=0 -PASSED=0 -FAILED=0 - -test_something() { - TOTAL=$((TOTAL + 1)) - if ./start some-command 2>&1 | grep -q "expected output"; then - PASSED=$((PASSED + 1)) - echo "✅ Test passed: something works" - else - FAILED=$((FAILED + 1)) - echo "❌ Test failed: something broken" - fi -} - -test_something -echo "Total: $TOTAL, Passed: $PASSED, Failed: $FAILED" -exit $FAILED -``` - -Add to test runner: - -```javascript -{ - name: 'My Shell Test', - command: './tests/my-shell-test.sh', - priority: 7, - critical: true, - type: 'shell', - parser: 'installation' -} -``` - ---- - -## Troubleshooting - -### "Server not accessible" Error - -```bash -# Ensure services are running -./start deploy - -# Wait for health check -curl -k https://localhost:4128/health - -# If not healthy, check logs -docker compose logs graphdone-api -``` - -### "Playwright not found" Error - -```bash -# Install Playwright -npm install -D @playwright/test -npx playwright install -``` - -### Certificate Errors - -```bash -# Regenerate development certificates -./scripts/generate-dev-certs.sh - -# Verify certificates exist -ls -la deployment/certs/ -``` - -### Tests Timeout - -```bash -# Increase timeout in test config -# Edit tests/run-pr-tests.js or tests/run-all-tests.js -const TEST_CONFIG = { - timeout: 120000, // 2 minutes per test - ... -}; -``` - -### Database Connection Failures - -```bash -# Ensure Neo4j is running -docker ps | grep neo4j - -# Check Neo4j logs -docker logs graphdone-neo4j - -# Restart Neo4j if needed -./start stop -./start deploy -``` - ---- - -## Test Maintenance - -### Monthly Review Checklist - -- [ ] Run `npm run test:comprehensive` and ensure all tests pass -- [ ] Review and update OAuth tests against latest provider documentation -- [ ] Check for outdated dependencies in test frameworks -- [ ] Verify test coverage for new features added in past month -- [ ] Update test documentation if new patterns introduced -- [ ] Review CI workflow performance and optimize if needed - -### OAuth Test Maintenance - -See [docs/oauth-testing-guide.md](./oauth-testing-guide.md) for OAuth-specific maintenance: -- Monthly spec review (every 12th) -- Provider documentation updates -- Token handling validation -- Profile schema changes - -### Test Performance Optimization - -**If tests are running too slowly**: - -1. **Parallelize independent tests** - Use Playwright's built-in parallelization -2. **Mock external services** - Use mock OAuth server for faster tests -3. **Reduce wait times** - Optimize timeouts and polling intervals -4. **Cache builds** - Reuse Docker images when possible - -**Benchmarks**: -- PR critical tests: Target < 3 minutes -- Comprehensive tests: Target < 7 minutes -- Individual test suites: Target < 60 seconds - ---- - -## Best Practices - -### Before Submitting a PR - -1. ✅ **Run PR tests locally** - `npm run test:pr` -2. ✅ **Fix all failures** - Do not submit PR with failing tests -3. ✅ **Review test report** - Check for warnings or skipped tests -4. ✅ **Test on clean environment** - `./start stop && ./start deploy` -5. ✅ **Verify HTTPS mode** - Ensure TLS/SSL tests pass - -### When PR Tests Fail in CI - -1. **Download test artifacts** - Get HTML report from GitHub Actions -2. **Review error details** - Expand failed test suites in report -3. **Reproduce locally** - Run same test suite with `npm run test:pr` -4. **Check for environment differences** - CI uses different certificates, ports -5. **Fix and re-test** - Push fix and wait for CI to re-run - -### Writing Reliable Tests - -- ✅ **Use auth helpers** - Leverage `tests/helpers/auth.ts` for consistent login -- ✅ **Wait for elements** - Use `await expect().toBeVisible()` not `page.waitForTimeout()` -- ✅ **Test data isolation** - Create unique test data, don't rely on shared state -- ✅ **Handle flakiness** - Add retries for network-dependent tests -- ✅ **Clear error messages** - Use descriptive test names and assertions - ---- - -## Test Commands Reference - -```bash -# PR validation (critical tests only) -npm run test:pr - -# Comprehensive validation (all tests) -npm run test:comprehensive - -# Individual test suites -npm run test:e2e # All E2E tests -npm run test:e2e:ui # UI mode (interactive) -npm run test:e2e:debug # Debug mode -npm run test:installation # Installation script tests -npm run test:https # TLS/SSL tests - -# Unit tests -npm run test:unit # All unit tests -npm run test:coverage # With coverage report - -# Test reports -npm run test:report # Open HTML report -open test-results/reports/index.html -open test-results/reports/pr-report.html -``` - ---- - -## Support - -**Documentation**: -- [OAuth Testing Guide](./oauth-testing-guide.md) - OAuth-specific testing -- [OAuth Implementation Guide](./oauth-implementation.md) - OAuth compliance tracking -- [TLS/SSL Setup](./tls-ssl-setup.md) - HTTPS configuration - -**Test Code**: -- Test runner: `tests/run-pr-tests.js` (PR), `tests/run-all-tests.js` (comprehensive) -- Auth helpers: `tests/helpers/auth.ts` -- OAuth mock server: `tests/helpers/mock-oauth-server.ts` -- Test fixtures: `tests/fixtures/oauth-profiles.ts` -- E2E tests: `tests/e2e/*.spec.ts` - -**CI/CD**: -- Workflow: `.github/workflows/ci.yml` -- PR validation job: `pr-validation` - ---- - -**Document Version:** 1.0.0 -**Last Updated:** 2025-11-12 -**Maintained By:** GraphDone Team -**Review Frequency:** Monthly diff --git a/docs/product/CORE_HOSTED_STRATEGY.md b/docs/product/CORE_HOSTED_STRATEGY.md new file mode 100644 index 00000000..e91b725f --- /dev/null +++ b/docs/product/CORE_HOSTED_STRATEGY.md @@ -0,0 +1,60 @@ +# GraphDone: human work with dependable agent check-ins + +Decision record, 2026-09-08. This supersedes older "pet agent", graph-as-agent-dashboard and enterprise-feature-gating proposals. The primary object is the person's work: what matters next, what is moving, and what needs their answer. A graph is an optional useful view of relationships, not a prerequisite for managing agents. + +## Product boundary + +| Capability | Core: free, open source, self-hostable | Hosted service | +| --- | --- | --- | +| Projects, tasks, priorities, relationships, notes | Full working product | Same product | +| Portable data, backup/restore, agent protocol | Open formats and interfaces; no artificial export restrictions | Managed persistence, backup operations and cross-device convenience | +| Feedback inbox, admin review, operator API | Same shared implementation; SQLite storage | D1 storage, quota admission, service operations | +| Bring an agent or inference gateway | Documented open API, scoped credentials, open runner examples | Guided setup and managed connections | +| Agent execution | Run adapters/runners on your own hardware/VPS | Optional managed per-tenant sandboxes, resumable sessions, scheduled work and usage support | +| Limits | Hardware and operator-configured safeguards | Published allowances tied to actual infrastructure/inference cost | +| Payment | No paywall for the collaboration primitives | Pay for saved setup/maintenance effort, hosted resources and operational guarantees | + +The paid edition must not depend on hiding essential task or agent capabilities from Core. Providers should be able to offer Core hosting. We should earn subscription revenue by being the easiest, most dependable place to use it. Hosting-provider marketplace listings are separate publishing work: a Docker Compose installer is not a claim of Hostinger marketplace availability. + +## Human-facing interaction + +1. A person writes an outcome or marks a task ready for help. +2. A connected agent reads the project's priorities and recent notes using a scoped token. +3. It posts a short plan/check-in, works within its permitted environment, and records evidence or an artifact. +4. If it needs a decision, it leaves one clear question and marks the task blocked. If it produced a result, it requests review. +5. The person answers or accepts the result in the existing task/notes interface. Provider, session, model, runner health and token controls sit behind details. + +Never say "working" just because a process exists. A useful check-in says what changed, what remains, and whether the human must act. Never mark work complete merely because the model's turn ended. A task completion must carry evidence appropriate to the task. A runtime failure or budget stop becomes a visible check-in, not disappearance. + +## Four distinct layers + +- **Work and coordination:** GraphDone projects/tasks/notes, feedback reports and the relevant scoped APIs. Independent of model vendor. +- **Inference:** LiteLLM or a direct provider. This is a model gateway, not a process/session manager. +- **Execution adapters:** Codex, Claude Agent SDK, Hermes or another runner. Translate runtime events into short task check-ins and preserve runtime session IDs privately. +- **Hosting/control:** tenant identity, run leases, secrets, queues, VM/container lifecycle, persistence, limits, stop/resume and audit. This is where hosted convenience earns its price. + +Keep D1 for compact application/coordination state. Do not stream every token, terminal line or screenshot into D1. Buffer events in the runner, write meaningful state transitions, and store large artifacts/transcripts in suitable file/object storage with a retention policy. Existing usage counters remain outside D1; an admin overview reports our measured admission/circuit-breaker totals, not an invented Cloudflare billing balance. + +## Verified runtime interfaces and integration choice + +**Codex:** App Server exposes initialized connections, threads and turns, including thread resume. The SDK provides programmatic integration. A private adapter can preserve the thread/session reference and send progress/approval events to GraphDone. The app server should sit behind our authenticated control plane inside the tenant's execution environment. We must test the installed schema rather than assume a CLI version supports every documented method. Sources: [App Server](https://learn.chatgpt.com/docs/app-server), [SDK](https://learn.chatgpt.com/docs/codex-sdk). + +**Claude:** The Agent SDK hosting example separates process, session and container, and describes durable session storage plus routing/spawn/lifecycle responsibilities. Start with one controlled container per tenant/run and persistent session data. A plain example HTTP server is not a production authentication boundary. Resume sessions through the adapter; map genuine tool outcomes to work-item notes. Source: [Anthropic hosting guide](https://platform.claude.com/cookbook/claude-agent-sdk-07-hosting-the-agent). + +**Hermes:** Its authenticated API offers session operations, event streaming and capability discovery. Profile routing has its own key boundaries. The server exposes powerful tools, including terminal execution, so GraphDone should talk through a private adapter, not give a browser a general runtime key. Detect capability/version support before enabling controls. Source: [Hermes API server](https://hermes-agent.nousresearch.com/docs/user-guide/features/api-server/). + +**LiteLLM:** Virtual keys support model restrictions, budget and concurrency/rate constraints. Put provider credentials in the runner/control plane, not the static SPA or task notes. Use a separate constrained key per hosted tenant or workload; record which model alias was actually used. A gateway alias does not establish whether the underlying provider is OpenAI, Anthropic or a local model. Source: [LiteLLM virtual keys](https://docs.litellm.ai/docs/proxy/virtual_keys). + +These are integration findings and architecture decisions, not claims that GraphDone already hosts all three runtimes. + +## Iterative acceptance gates + +| Stage | Deliverable | Evidence required before enabling it | +| --- | --- | --- | +| Operations foundation | Reports, human admin, scoped improvement access, usage overview | Same contract against Core SQLite and Worker/D1; headed reporter-to-review journey; token denial/revocation; stored audit | +| Bounded collaboration | Open runner reads work/reports and produces a useful check-in | Synthetic local fixtures, strict action validation, small serial gateway calls, one failed/blocked run made visible | +| Bring-your-own running session | Private Codex/Claude/Hermes adapters | Real session start/resume/stop; reconnect without duplicate notes; cross-tenant denial; secrets never in browser payloads | +| Managed execution | Queue + lease + isolated sandbox per tenant/run | Competing claim test, lease expiry, process crash, budget stop, egress boundaries, persistent work recovery, operator kill switch | +| Hosted paid experience | Guided setup, managed storage/backups and execution allowance | Published pricing/limits, cancellation/export, backup restore drills and a human-first usability test | + +Do not build a marketplace of nonfunctional buttons. Expose only capabilities proven by the connected runtime. Keep explicit gaps in the release ledger. The current Neo4j Core and hosted D1 paths still need parity work on offline sync and portable transfers; that is engineering debt to close, not a monetization strategy. diff --git a/docs/quality/AUTH_LINK_LIFECYCLE.md b/docs/quality/AUTH_LINK_LIFECYCLE.md new file mode 100644 index 00000000..c4a2cd09 --- /dev/null +++ b/docs/quality/AUTH_LINK_LIFECYCLE.md @@ -0,0 +1,73 @@ +# Email link purpose and session lifetime + +This contract is implemented by the SQLite authentication path. The token work +for #184 depends on the server CAPTCHA policy in #195; provider email proof and +OAuth account linking remain a separate review. + +## Purpose and migration + +Every newly issued email link stores `login` or `password-reset`, the account +ID when one exists, and its current `authVersion`. Login redemption only claims +login links. Password reset only accepts a reset link bound to an active account +at the same revision. A wrong-purpose request leaves the link available for its +correct purpose; expiry and one-use checks occur in the SQLite write predicate. + +Startup adds the new columns idempotently and marks old untyped links used. +People with old emails must request a new link; migration does not guess a +purpose. Existing users start at revision zero. A correctly signed legacy JWT +without a revision is interpreted as zero, so it continues until its existing +expiry or the account's first revision change. Invalid/negative/non-integer +revision claims are rejected. New JWTs always carry an explicit revision. + +## Reset and account status + +Reset performs one conditional SQLite user update: password hash and revision +change together, only if the reset token is still unexpired, unused, correctly +purposed and bound to the current account revision. A trigger in that same +statement revokes outstanding links. If revocation fails, the password/revision +update rolls back. Concurrent reset attempts can produce only one success. +Administrative password updates use the same revision/trigger mechanism. + +Disabling or reactivating an account changes its revision whenever status +changes. Repeating an unchanged status does not mint a new revision. Reactivation +therefore cannot revive an earlier session or link. The migration also supplies +the existing status API's missing `deactivationDate` column, and status updates +return the inactive account without making it available to authentication. + +HTTP GraphQL context, share-link creation and human sessions in the agent and +operations APIs verify the current active account and revision. Existing scoped +`gd_agent_` and `gd_ops_` credentials retain their separate expiry, revocation, +scope and active-owner checks; password reset does not revoke those credentials. +WebSocket connection authentication and every +outgoing result recheck the revision. A rejected socket closes with code4403; +an idle transport can remain open until its next operation/result, without +restoring authorization. Requests already authorized before a concurrent reset +are not retroactively undone. Refresh rejects an authorization snapshot whose +revision no longer matches. Password/OAuth issuers sign the revision of the +identity they authenticated; login-link redemption rejects a revision change +between consumption and identity lookup. + +## Evidence and boundaries + +`packages/server/tests/auth-link-boundary.test.ts` mounts the production email +handlers with real in-memory SQLite, Apollo HTTP and graphql-ws transports. It +covers cross-purpose rejection and subsequent correct use, reset/disable/ +reactivate revocation, an already connected socket, fresh login, concurrent +reset, trigger rollback, old-schema migration, delayed expiry and stale refresh/ +identity snapshots. `magic-link.test.ts` retains login concurrency, replay, +expiry and database-error controls. Production startup tests retain environment +loading and signing-secret checks. Owned-fixture CAPTCHA proofs are genuinely +solved and verified; the test uses a bounded Node SHA-256 loop to avoid async +WebCrypto scheduling costs. It does not lower production challenge difficulty. + +The same suite mounts the real agent and operations routers and attempts +credential issuance with reset and disabled/reactivated JWTs. The operations +store is real in-memory SQLite; the agent graph driver is a bounded test adapter, +so these cases prove the HTTP authorization boundary, not Neo4j execution. Both +stale-session paths must return401 without issuing a credential or querying graph +content, while a fresh password login must succeed. + +These tests do not exercise live email delivery, a provider callback, Cloud D1, +shared databases or a deployment. The old unused Neo4j authentication modules +are not asserted to implement this contract. Provider subject/verified-email +rules and linked-account recovery remain the OAuth portion of #184. diff --git a/docs/quality/CORE_VALIDATION_2026-10-23.md b/docs/quality/CORE_VALIDATION_2026-10-23.md new file mode 100644 index 00000000..31d5f5f7 --- /dev/null +++ b/docs/quality/CORE_VALIDATION_2026-10-23.md @@ -0,0 +1,404 @@ +# Core merge validation — 2026-10-23 + +## Current disposition — 2026-10-23, 22:29 UTC snapshot + +The final frozen PR #157 head +`385e7679fabc60b80cd65cf0a1aa4cdf3d592514` passed public +[CI run 40025965786](https://github.com/GraphDone/GraphDone-Core/actions/runs/40025965786) +and [Comprehensive Test Suite run 40025965837](https://github.com/GraphDone/GraphDone-Core/actions/runs/40025965837). +The independently inspected +[browser artifact 12533273048](https://github.com/GraphDone/GraphDone-Core/actions/runs/40025965786/artifacts/12533273048) +records smoke **6**, new-user **42**, mobile **27** and performance **3** passes; +the separate living-graph suite passed **3**. The completed validation also +records **603 unit passes with 26 explicitly DB-gated skips**, **161 harness +passes** and **9 real disposable-database contract passes**. Required browser +cases did not pass through skips or flaky retries. Earlier failures below remain +part of the record. + +After the owner's explicit standing authorization changed **only `dev` +administrator enforcement**, PR #157 merged through native `--admin` at +`9068cd31824991330c6d92196654e53e04e29063`. Its parents are +`8a7de052eb60fe517f45681dc42b5b169d09c660` and the frozen `385e767` head; +its entire tree `6bf014bcc30eb6e21c0deeae4db9c7d4effeaac2` equals that validated +source. Other protections remain unchanged. This was an owner-authorized +administrative merge, not a non-author approval, deployment or hosted-acceptance +claim. See the [current dev integration policy](../../.agents/playbooks/dev-admin-integration.md). + +Post-merge CI was still running at this snapshot. Final checks on the latest +resulting `dev` head and issue closure are not claimed here; +[issue #156](https://github.com/GraphDone/GraphDone-Core/issues/156) is the +authoritative post-merge/closeout record. The sections below preserve earlier +snapshots: their **PENDING** review/CI wording describes those earlier states, +not a reversal of the confirmed results above. Remaining release limits still +apply. + +## Follow-up: the fresh production audit held final approval + +The snapshot below remains historical evidence. On the subsequent clean merged +head `78a90cc7899e44c33aa27117bccb19ea7cbe85f6`, a new pinned installation passed +build, 590 unit tests (25 explicitly DB-gated skips), 161 harness tests, types, +lint, bundle and eight real Neo4j contract checks. Its production high/critical +audit instead exited 1 with eight moderate findings plus the Zod high advisory +[GHSA-mrhx-c7xc-m3w5](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5). That +fresh failure blocked final approval despite the earlier green CI response. +The exact metadata/cache cause of the discrepancy is not established. + +The [bounded-validation and consumer-compatibility lesson](../../.agents/inbox/2026-10-23-zod-constrained-collections.md) +records the targeted Zod 4.6.7 correction and unchanged MCP SDK 1.32.2. The same +13 small regression fixtures fail five cases on the old dependency and pass +all 13 on the patch. The patch's build and production audit pass; a complete +unit run at `ea46e39` passes 603 cases with 25 DB-gated skips. The SDK's actual +dependency path is verified, including avoiding a nested patched copy that +would leave its hoisted dependency vulnerable. CI gives only the audit step a +fresh per-attempt cache; the threshold is unchanged. These results do not prove +unbounded-collection protection, full MCP denial-of-service resistance or a +hosted deployment. Final combined public CI and protected review remain pending. + +The subsequent combined candidate +`df1fac165a5f1421602c3706db8b86090b54a4f5` includes the separate MCP stdout +correction in [PR #179](https://github.com/GraphDone/GraphDone-Core/pull/179) +and the dependency correction in [PR #180](https://github.com/GraphDone/GraphDone-Core/pull/180). +A fresh install on that clean, unchanged source passes build, **603 unit cases +with 26 DB-gated skips**, **161 harness cases**, types, lint, bundle, and the +fresh-cache production audit (zero high/critical, eight moderate). All **nine +real Neo4j contract cases** pass, including the actual MCP entry point's +create/read/error/recovery path, verified fixture cleanup and zero protocol +parse errors through shutdown. A separate compiled production-mode MCP probe +also initializes, lists 31 tools, reads an owned two-node/one-edge graph, +rejects malformed/unknown calls and recovers, with zero stdout protocol errors +and confirmed child/edge/node/graph cleanup. It is a one-off probe, distinct +from the committed nine-case contract. Evidence is retained in +`core-mcp-final-candidate-20261023/test-artifacts/audit-20261023/{final-mcp-combined,zod-stdio}/`. +These local combined results do not replace final public-head CI or a real +non-author approval of protected PR #157. + +## Earlier functional integration snapshot + +**Snapshot: all planned local gates pass and the scoped stack is merged into +the public audit branch. Final public integration CI and protected `dev` merge +approval remain PENDING.** Public functional integration: +`84f1c0d132a6fda6a6bc13270363cf18b69a89f0`. Its entire Git tree +`f5a394d442193a502ffa144022fcdcfeb8889f90` matches the tested local candidate +`bdf82ccdd79bbc4c21ce4833023f78596a1be4a8`, including reviewed test/lesson-only +PR #177. That candidate's only changes from `e120df4b849969854ffe529a64df3d42498ae4cf` are +the touch test and two lesson/index files; no application code changed. The +passing new-user, smoke and performance runs tested clean, unchanged `e120df4`, +which includes reviewed evidence-only PR #176. Earlier +new-user and mobile runs failed on clean frozen +`f8a11e16a593738d485547eb1dfe117438cc3ed6`. Earlier combined checks ran on clean +`b009245035559fccadd350a02c106b6da42347c6`. Between `b009245` and `f8a11e1`, a +type-only server annotation and performance-evidence update left all 35 rebuilt +runtime JavaScript files unchanged (`type-only-runtime-equivalence.json`). +PR #176 adds diagnostics and safe evidence retention, not an application fix. +Do not relabel earlier runs as having run at a different SHA. These Core merge +checks do not establish deployed health, hosted D1 parity or fulfillment of all +62 [new-user acceptance questions](NEW_USER_GATE.md). A separate, bounded live +entry-page observation is recorded below. + +The audit uses Node 22.23.4, lockfile-installed dependencies, bundled Chromium, +and an owned local Core/Neo4j stack (web `127.0.0.1:13127`, API `127.0.0.1:14127`). +Required new-user, mobile and performance runs explicitly use one worker and +zero retries; the standalone living-graph run also pins zero retries. The smoke +manifest inherits the configured retry count (two in CI), while its strict +adapter rejects flaky or retried outcomes. Mutable fixtures belong to each test; +delete edges before work items and their graph, stopping cleanup on error. +Never enable the disposable-database contract marker against a shared database. + +## Separate live entry-page observation + +At **2026-10-23 20:42 UTC**, a read-only browser probe of +`https://graphdone.com` at 1440×900 and 390×844 recorded HTTP 200, visible guest +and email-entry controls, no JavaScript/request/HTTP errors and no horizontal +overflow. The desktop and phone screenshots were inspected. No control was +clicked, no form was submitted and no data was authored, so this establishes +entry-page availability only; it does not prove authentication, guest data, +email delivery, graph operations or persistence. + +The first probe at 20:40 UTC timed out waiting for a `main` element despite +HTTP 200 and the rendered sign-in controls. Both pages recorded `mainCount: 0` +in the corrected controls-based probe. Retain that failed assumption separately +from the successful entry check: it is not outage evidence, and the missing +main landmark remains an accessibility limitation, not a passed accessibility +audit. + +The public release metadata still reported Core +`f8fb968567dbd80767ea738d4ab9892c1042bdd7`, Cloud +`304629e0ef0b294a0025977a87c2cf106dfe41b9`, built +`2026-09-13T23:38:21.838Z`. The public health response reports worker liveness +with `db: not_probed`; it is not database health evidence. These observations +do not show deployment of this audit's reviewed changes. JSON reports and +screenshots are retained separately under the Cloud checkout's +`test-artifacts/assessment-2026-10-23/{live-browser,live-browser-controls}/`. + +## Public review and integration state + +The 19 scoped PRs below are now merged, directly or through their stacked parent +branches, into `audit/core-validation-20261023`. The final integration merge +#161 was confirmed publicly at `84f1c0d` on 2026-10-23 at 20:48 UTC. This is not +a merge to `dev` or a deployment. [Integration PR #157](https://github.com/GraphDone/GraphDone-Core/pull/157) +remains open; required final CI and human approval remain pending. The five-file +documentation follow-up is based on `84f1c0d`, so it does not republish the audit +implementation as a documentation patch. + +The matrix preserves each original reviewed scoped head. Stacked child merges +advanced some parent heads; the separate merge column records the actual +confirmed merge and its target. The retained audit merge record and Git +ancestry checks establish that all 16 new merge commits, plus earlier +#159/#164/#165, are included in `84f1c0d`. Independently compared Git trees are +identical between that public composition and tested `bdf82cc`. + +The API merge attempt for #170 stopped on an OAuth `workflow`-scope refusal. +The already configured SSH credential was verified as the same account; +ordinary merge pushes subsequently completed #170, #174 and #161, with their +remote/PR states checked. [The operational record](../../.agents/inbox/2026-10-23-pr-base-ref-is-not-live-tip.md#a-workflow-scoped-api-refusal-needs-its-own-evidence) +preserves the rejected command, identity response and confirmed follow-up +commits. No permissions were expanded, and `dev` protection remains unchanged. + +| PR | Scope | Reviewed scoped head | Confirmed merge | +| --- | --- | --- | --- | +| [#159](https://github.com/GraphDone/GraphDone-Core/pull/159) | Strict evidence and owned output paths | [13acecc](https://github.com/GraphDone/GraphDone-Core/commit/13acecc9cce2b391c10b2ceb87adb9350a4f13b9) | Merged into audit integration: [7504d0d](https://github.com/GraphDone/GraphDone-Core/commit/7504d0daa229eb5a8fe93ecbbb40d6e478183669) | +| [#164](https://github.com/GraphDone/GraphDone-Core/pull/164) | Owned process-group timeout cleanup | [f9d89cb](https://github.com/GraphDone/GraphDone-Core/commit/f9d89cb2939d1acfb6919dc2355990f29d3f9ef0) | Merged into audit integration: [1f04f9b](https://github.com/GraphDone/GraphDone-Core/commit/1f04f9b7cb5567b48f49a8e27493881f6cc17fa5) | +| [#165](https://github.com/GraphDone/GraphDone-Core/pull/165) | Deterministic MCP CPU tests; production guard retained | [d0561bf](https://github.com/GraphDone/GraphDone-Core/commit/d0561bf05bf551708b75745fcb64c2f23209e4aa) | Merged into audit integration: [100625e](https://github.com/GraphDone/GraphDone-Core/commit/100625e58a01d282082e9d1fd74d61020f01f842) | +| [#158](https://github.com/GraphDone/GraphDone-Core/pull/158) | Layout queue lifetime/owner isolation; safe bulk deletion | [f364a9c](https://github.com/GraphDone/GraphDone-Core/commit/f364a9cee0cbd594478be0941088098c0b0af6ce) | Merged into audit integration: [1fb39b0](https://github.com/GraphDone/GraphDone-Core/commit/1fb39b004b94ede6868727eb8b7fa5ca2e902ed2) | +| [#160](https://github.com/GraphDone/GraphDone-Core/pull/160) | Folder/current-role and descendant authorization | [ec8090d](https://github.com/GraphDone/GraphDone-Core/commit/ec8090d5df6d299e752ece9e74c081b178c1317d) | Merged into audit integration: [5de5d52](https://github.com/GraphDone/GraphDone-Core/commit/5de5d5278e2c9d93c196a01a0b529b0e7c62cdcc) | +| [#161](https://github.com/GraphDone/GraphDone-Core/pull/161) | Node/Apollo/dependency/runtime and CI gates | [5f0f454](https://github.com/GraphDone/GraphDone-Core/commit/5f0f454259f45a847de14e6ccc6f05f426b6868e) | Merged into audit integration: [84f1c0d](https://github.com/GraphDone/GraphDone-Core/commit/84f1c0d132a6fda6a6bc13270363cf18b69a89f0) | +| [#163](https://github.com/GraphDone/GraphDone-Core/pull/163) | Mobile login/admin fixture controls | [c04fbac](https://github.com/GraphDone/GraphDone-Core/commit/c04fbacf792303893cdf60669873c87221866936) | Merged into [#161's branch](https://github.com/GraphDone/GraphDone-Core/pull/161): [c8d634d](https://github.com/GraphDone/GraphDone-Core/commit/c8d634d3a8e95f9a06a73760a1e9599dcd795791) | +| [#166](https://github.com/GraphDone/GraphDone-Core/pull/166) | Saved-state and recorded-AI browser fixtures | [e6cf1ac](https://github.com/GraphDone/GraphDone-Core/commit/e6cf1ace2bf5addb4cbcba7b13fab20ddc599210) | Merged into [#163's branch](https://github.com/GraphDone/GraphDone-Core/pull/163): [827a854](https://github.com/GraphDone/GraphDone-Core/commit/827a854ede8f18840aa410ec6f8b4da4eee5d3bc) | +| [#167](https://github.com/GraphDone/GraphDone-Core/pull/167) | Real drag/failure/Graph–List–Graph recovery | [5a4a8e3](https://github.com/GraphDone/GraphDone-Core/commit/5a4a8e3fbe3fd5db500e83f010799fe01d596ea7) | Merged into [#161's branch](https://github.com/GraphDone/GraphDone-Core/pull/161): [eed5c6b](https://github.com/GraphDone/GraphDone-Core/commit/eed5c6b82f04c7372590d87655751b8e43368366) | +| [#168](https://github.com/GraphDone/GraphDone-Core/pull/168) | Lint matcher correction; residual security inventory | [ddb4407](https://github.com/GraphDone/GraphDone-Core/commit/ddb44079ed2f8f3f1d702716a1711f5885283911) | Merged into [#161's branch](https://github.com/GraphDone/GraphDone-Core/pull/161): [dd447c6](https://github.com/GraphDone/GraphDone-Core/commit/dd447c6df44afb4ed3f51992be49aaaa55019699) | +| [#169](https://github.com/GraphDone/GraphDone-Core/pull/169) | Single-use magic-link consumption | [7e91d1a](https://github.com/GraphDone/GraphDone-Core/commit/7e91d1a59c9ae9051569d8ef711ef6619f921bd6) | Merged into [#161's branch](https://github.com/GraphDone/GraphDone-Core/pull/161): [8b93344](https://github.com/GraphDone/GraphDone-Core/commit/8b93344e985d314fb9b75dfe846d174a772fb56a) | +| [#170](https://github.com/GraphDone/GraphDone-Core/pull/170) | Production signing-secret startup validation | [bc483d2](https://github.com/GraphDone/GraphDone-Core/commit/bc483d21d74e787d7807a909757fb2538e0cea14) | Merged into [#161's branch](https://github.com/GraphDone/GraphDone-Core/pull/161): [dcbd5e2](https://github.com/GraphDone/GraphDone-Core/commit/dcbd5e29ae68e90788855d86b15455b2374fdbd6) | +| [#171](https://github.com/GraphDone/GraphDone-Core/pull/171) | Server-side GraphQL password-attempt accounting | [46388cb](https://github.com/GraphDone/GraphDone-Core/commit/46388cb62acfa198f13b294621216bf6cb1cb799) | Merged into [#161's branch](https://github.com/GraphDone/GraphDone-Core/pull/161): [69d36e3](https://github.com/GraphDone/GraphDone-Core/commit/69d36e33d26336892ee705dfe3e922dbb28a7a6d) | +| [#172](https://github.com/GraphDone/GraphDone-Core/pull/172) | Rendered navigation/camera acceptance | [041fa51](https://github.com/GraphDone/GraphDone-Core/commit/041fa51ace6d056db2108ce15779c6ea193e01a0) | Merged into [#166's branch](https://github.com/GraphDone/GraphDone-Core/pull/166): [25838b4](https://github.com/GraphDone/GraphDone-Core/commit/25838b405cf78079c94b231409747679e42ffe95) | +| [#173](https://github.com/GraphDone/GraphDone-Core/pull/173) | Owned mobile-dialog fixtures and Escape dismissal | [3c648cb](https://github.com/GraphDone/GraphDone-Core/commit/3c648cbbe3a08b664492bb0283dea6ecfa1572d4) | Merged into [#172's branch](https://github.com/GraphDone/GraphDone-Core/pull/172): [7fec50b](https://github.com/GraphDone/GraphDone-Core/commit/7fec50b5437ff5561cd884fa1558835aa3bbded5) | +| [#174](https://github.com/GraphDone/GraphDone-Core/pull/174) | Newer draft retention and scoped undo completion | [9b0c050](https://github.com/GraphDone/GraphDone-Core/commit/9b0c05035b5d562a74978badd076680ed86dded7) | Merged into [#161's branch](https://github.com/GraphDone/GraphDone-Core/pull/161): [5f834b1](https://github.com/GraphDone/GraphDone-Core/commit/5f834b1947face3b281093a00a1a29e18ab135c2) | +| [#175](https://github.com/GraphDone/GraphDone-Core/pull/175) | Measured first/final graph metrics; strict mobile/perf gates | [1a16d4e](https://github.com/GraphDone/GraphDone-Core/commit/1a16d4ea9a5e2b33fe8604336351a7e08a76d73c) | Merged into [#161's branch](https://github.com/GraphDone/GraphDone-Core/pull/161): [285f462](https://github.com/GraphDone/GraphDone-Core/commit/285f4623d6756ea8647c9d93cd6395cebe81d4c2) | +| [#176](https://github.com/GraphDone/GraphDone-Core/pull/176) | Failure traces/route diagnostics and owned, symlink-safe attachment copying | [a494741](https://github.com/GraphDone/GraphDone-Core/commit/a494741d0d3e58910b903135ec8898e00697918a) | Merged into [#175's branch](https://github.com/GraphDone/GraphDone-Core/pull/175): [0f12b1d](https://github.com/GraphDone/GraphDone-Core/commit/0f12b1d36aca0058eb1edf12c034ca44247a695a) | +| [#177](https://github.com/GraphDone/GraphDone-Core/pull/177) | Owned locked-touch editing, exact saved-title/readback and reload acceptance | [98c5b73](https://github.com/GraphDone/GraphDone-Core/commit/98c5b7313130bedea243ce53abfb36e86ce5bb2e) | Merged into [#173's branch](https://github.com/GraphDone/GraphDone-Core/pull/173): [a4bc803](https://github.com/GraphDone/GraphDone-Core/commit/a4bc803713390578737274138f1c6f6d31f8b6c2) | + +## Retained failures and corrective evidence + +The first strict 42-case browser gate on clean +`aece6797ef61b21665c924762fb8c9ff68870c3a` finished with **36 passed and six failed +browser cases**. The adapter added one synthetic failed execution-evidence case +for process exit 1, so its report contains **43 cases: 36 passed, seven failed**. +The synthetic case is not a seventh product failure. The report records unchanged +source throughout, `timedOut: false`, and zero skips. Retain the **core-validation +worktree's** `test-artifacts/audit-20261023/strict-new-user/{report.json,report.html}` +and screenshots; these are not in the final-candidate worktree. Do not replace +them with a later passing run. + +The six browser failures were button-content acceptance; details/date saving at +390px; motion at 844px and 1440px; gradual resize/framing; and all-mode navigation +at 844×390. Five led to [responsive fixture/measurement corrections](../../.agents/inbox/2026-10-23-responsive-acceptance.md). +The sixth exposed the [delayed details-save product race](DETAILS_SAVE_COMPLETION_2026-10-23.md). +An unchanged isolated details rerun passed; deterministic Apollo regressions +retain the failing-before evidence instead of treating that retry as a fix. + +The completed strict new-user run on `f8a11e1` then recorded **41 actual browser +passes and one actual failure**, plus one synthetic execution-evidence failure: +**43 reported cases, 41 passed/two failed**, exit 1, in **508 seconds**. Source +and fingerprints stayed unchanged, with zero skips and no process timeout. The +button sweep failed while only the pre-React static shell was visible: `main` +was unavailable at its existing deadline and the artifact showed +**GraphDone Loading...**. +Docker build/export activity overlapped that run; this is an observation, not +proof of resource contention or the cause of the startup failure. + +The completed strict mobile run on the same frozen `f8a11e1` recorded **26 actual +passes and one actual failure**, plus one synthetic execution-evidence failure: +**28 reported cases, 26 passed/two failed**, exit 1, in **246 seconds**. Its source +and fingerprints also stayed unchanged, with zero skips and no process timeout. +The failed case expected a node inspector after a single tap; the error DOM +contained **Edit work item** instead. Source review confirmed that touch taps +intentionally open this editor while mouse selection uses the inspector. +[PR #177's dated lesson](https://github.com/GraphDone/GraphDone-Core/blob/98c5b7313130bedea243ce53abfb36e86ce5bb2e/.agents/inbox/2026-10-23-mobile-touch-editor.md) +records the correction: an owned locked graph/task, actual touch input, exact +save acknowledgement, independent readback and reload with unchanged coordinates. +The focused case passed in 14.729s against unchanged `e120df4` application code; +two earlier test-selector failures are retained. The later complete mobile +rerun passed all 27 cases on clean, unchanged `bdf82cc` in 258.703s, exit 0, with +zero failed/skipped cases, no timeout and no evidence errors. Both source +snapshots share fingerprint +`df0dc48120d54af515193eb0be5d74146850bdfaeccc4098dc2dce424ede11b5`. +Its distinct `final-mobile-traced` report preserves the earlier failed run. + +[PR #176](https://github.com/GraphDone/GraphDone-Core/pull/176) retains failure-only +traces, labels route/viewport steps and keeps bounded diagnostic attachments. +It restricts downloadable copies to regular files inside the owned artifact +root, rejecting outside-root paths and symlink traversal. It does not change +application behavior, deadlines, retries or acceptance thresholds. The +serialized diagnostic rerun on `e120df4` **passed all 42 cases**, with zero +failed, skipped or synthetic cases, exit 0 and no timeout, in **588.361 seconds**. +Both source snapshots were clean, with unchanged fingerprint +`5d8eb5cc8e034173785f76f3ca5b6b400f8f1388ec71771e3a6ab2e3043f0636`. +The recorded command used the full 12-file new-user selection, headed Chromium, +one worker, zero retries and `--trace=retain-on-failure`. Retain +`final-new-user-traced/{report.json,report.html,sequences/new-user.json}` alongside +the earlier failure. This pass does not identify the startup failure's cause or +establish a runtime fix. + +Historical [PR #173 CI run 40013727616](https://github.com/GraphDone/GraphDone-Core/actions/runs/40013727616) +checked merge `cc4e8efca776b79c1be8a725943660037d1deff0` (`3c648cb` into +`041fa51`). Its older direct new-user command discovered 41 cases and inherited +two retries: 40 passed and one was flaky (details at 390px passed on retry 1). +Mobile then recorded 26 passed and the same stale-inspector expectation failed. +That merge lacked #174's save-race fix and #167's added layout case. These are +historical CI observations, not strict acceptance or final integration CI. + +Other retained boundaries and their dated lessons: + +- [Live Git refs versus PR base metadata](../../.agents/inbox/2026-10-23-pr-base-ref-is-not-live-tip.md): + the read-only merge inventory stopped when PR `baseRefOid` values disagreed + with the actual target ref. The API cause remains unknown; record the live + target and verify the eventual merge head/tree before accepting composition. +- [Runtime/install/container and minimum-Node checks](RUNTIME_VALIDATION_2026-10-23.md), + [timeout/descendant cleanup](TEST_TIMEOUT_REVIEW_2026-10-23.md), and + [ambient-CPU test isolation](MCP_CPU_TESTING_2026-10-23.md). + Combined lint also caught `NodeJS.ProcessEnv` as `no-undef` after builds/types + passed. PR #170's type-only `Record` follow-up + preserves the guard behavior; its server lint, typecheck and 29 focused tests + pass. The corrected integrated lint result is recorded separately below. +- [Queued-save/deletion review](DURABILITY_REVIEW_2026-10-23.md) and its + [real-browser failure/recovery case](LAYOUT_RECOVERY_BROWSER_2026-10-23.md). +- [Saved-state/recorded-AI fixtures](NEW_USER_GATE_REVIEW_2026-10-23.md) and + [mobile audit corrections](../../.agents/inbox/2026-10-23-mobile-audit-fixtures.md). + The initial mobile selection recorded 15 passed, three failed and nine not run; + focused corrections do not count those nine as passing. +- [Performance evidence](../../.agents/inbox/2026-10-23-performance-gate-evidence.md): + the corrected owned fixture initially exposed two missing-metric failures and + one pass on the unchanged app. The corrected three-case browser result is + recorded below; a small placed fixture does not establish scale limits. +- [Legacy descendant-folder ownership](../../.agents/inbox/2026-10-23-folder-delete-legacy-hierarchy.md), + [magic-link replay](MAGIC_LINK_REPLAY_2026-10-23.md), + [production secrets](PRODUCTION_SECRETS_2026-10-23.md), and + [password-login limits](PASSWORD_LOGIN_LIMITS_2026-10-23.md). + These are bounded local tests, not a complete authentication/security audit. + +## Final combined evidence — to be completed before merge + +Completed runs retain their recorded verdict below; unexecuted gates remain +**PENDING**. Earlier scoped counts must not be copied here as combined results. +Fill each row with the command actually run, source SHA, +passed/failed/skipped/unexecuted counts, exit code and durable artifact/CI link. +For unified reports, verify matching start/end source fingerprints, intended +backend/target, complete discovery, and no missing, flaky or synthetic-failure +evidence. A successful wrapper exit or a skipped required case is not a pass. + +Unified browser reports capture their own start/end source. Other exact-head +attributions below use recorded audit commands and reflog, not a SHA embedded +in each log. Corrected lint ran at +`599ffc00d0d47f984cebed9a8f6fba57f6d77b46`; bundle validation ran at `f8a11e1`, +and audit/HTTP checks at `b009245`. The clean install preceded final composition; +its exact checkout was not captured, so it is not relabeled as `b009245`. The +installed lockfile remained unchanged through the candidate. + +| Gate and reproducible entry | Scope / required record | Final result | +| --- | --- | --- | +| `npm ci --legacy-peer-deps` | Clean pinned install before final composition; same lockfile, exact checkout not captured | **PASS**, exit 0. `final-candidate-clean-install.log` | +| `npm run perf:bundle` | Largest gzipped JS chunk 265.8kB against 450.0kB budget | **PASS**, `f8a11e1`, exit 0. `final-bundle-budget.log` | +| `npm run build`; `npm run typecheck` | All workspaces; preserve warnings | **PASS**, `b009245`, both exit 0. `final-integrated-build.log`, `final-integrated-types.log` | +| `npm run lint` | All workspaces after the type-only secret-validator correction | **PASS**, `599ffc0`, exit 0: zero errors, existing warnings remain. `final-integrated-lint-corrected.log` | +| `npm run test` | Combined core/web/server/MCP unit results; report DB-gated skips separately | **PASS**, `b009245`, exit 0: 590 passed (41 core, 326 web, 126 server, 97 MCP); 25 explicitly DB-gated skips. `final-integrated-unit.log` | +| `npm run test:unified:lib` | Discovery, evidence rejection, process ownership, dependency and performance-fixture contracts, plus #176 diagnostics | **PASS**, `e120df4`, exit 0: 161 passed, zero failed/skipped. `final-harness-with-diagnostics.log`. Earlier `b009245` result: 151 passed, exit 0, `final-integrated-harness.log` | +| Owned startup probes of the built API and generic container images | Configured secrets; `/health`, Neo4j and login/authenticated `me` readback; image IDs in JSON | **PASS**, `f8a11e1`: both images healthy with authenticated readback. `final-images-runtime.json`; exact probe shell command/exit not recorded in that JSON | +| Start the same two built images without production secrets | Negative startup control; expected process rejection | **PASS**, both reject missing secrets with expected exit 1. `final-image-secret-rejection.json`; same `f8a11e1` images per execution record | +| In `packages/mcp-server`: `RUN_DB_CONTRACT=1 GRAPHDONE_DISPOSABLE_NEO4J=1 npx vitest --run tests/neo4j-contract.test.ts` | Owned disposable Neo4j only | **PASS**, `b009245`, exit 0: 8 passed. `final-integrated-db-contract.log` | +| `python3 /tmp/graphdone-folder-http.py` | One-off owned-account HTTP probe, not a committed regression harness; scope below | **PASS**, `b009245`, exit 0: 8 checks. `final-folder-real-http.json` | +| `xvfb-run --auto-servernum node tests/run-unified.mjs --sequence smoke --out test-artifacts/audit-20261023/final-smoke` | Actual Core UI/API journeys, layout recovery and cleanup | **PASS**, unchanged clean `e120df4`, exit 0, 101.422s: 6 passed, zero failed/skipped, no timeout or evidence errors | +| `xvfb-run --auto-servernum node tests/run-unified.mjs --sequence new-user --out test-artifacts/audit-20261023/final-new-user` | Full strict headed selection, zero retries | **FAIL**, unchanged clean `f8a11e1`, exit 1, 508s: 41 actual passes/one actual failure plus one synthetic failure (43 report cases); zero skips, not timed out | +| `xvfb-run --auto-servernum node tests/run-unified.mjs --sequence new-user --out test-artifacts/audit-20261023/final-new-user-traced` | Serialized rerun with #176 retained route/trace evidence; headed, one worker, zero retries | **PASS**, unchanged clean `e120df4`, exit 0, 588.361s: 42 passed, zero failed/skipped/synthetic cases, not timed out | +| `xvfb-run --auto-servernum node tests/run-unified.mjs --sequence mobile --out test-artifacts/audit-20261023/final-mobile` | Complete mobile/audit selection, zero retries | **FAIL**, unchanged clean `f8a11e1`, exit 1, 246s: 26 actual passes/one actual failure plus one synthetic failure (28 report cases); zero skips, not timed out; #177 corrects the obsolete touch expectation | +| `xvfb-run --auto-servernum node tests/run-unified.mjs --sequence mobile --out test-artifacts/audit-20261023/final-mobile-traced` | Complete mobile/audit rerun including #177, one worker and zero retries | **PASS**, unchanged clean `bdf82cc`, exit 0, 258.703s: 27 passed, zero failed/skipped, no timeout or evidence errors | +| `TEST_URL=http://127.0.0.1:13127 QC_API_URL=http://127.0.0.1:14127/graphql xvfb-run --auto-servernum npx playwright test tests/perf/graph-perf.spec.ts --project=perf --headed --workers=1 --retries=0 --trace=on` | Owned placed graph, matching measured first/terminal samples, exact-data API samples and cleanup | **PASS**, `b009245`, exit 0: 3 passed in 33.9s, zero skips. `perf-final*` evidence; see dated performance lesson | +| `xvfb-run --auto-servernum node tests/run-unified.mjs --sequence perf-budgets --out test-artifacts/audit-20261023/final-perf` | Final local strict adapter execution of the same required performance selection | **PASS**, unchanged clean `e120df4`, exit 0, 31.533s: 3 passed, zero failed/skipped, no timeout or evidence errors | +| `xvfb-run --auto-servernum npx playwright test tests/e2e/graph/living-graph-effects.spec.ts --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 --trace=retain-on-failure --reporter=json` | Additional CI living-graph selection; keep its evidence separate from performance budgets | **PASS**, clean `bdf82cc` before/after, exit 0, 53.761s: 3 passed, zero skipped/unexpected/flaky. `final-living.json`, `final-living-provenance.json`, `final-living.log` | +| `npm audit --omit=dev --audit-level=high --json` | Actual production high/critical threshold; severity counts do not establish reachability | **PASS for high/critical gate**, `b009245`, exit 0: zero high, zero critical, eight moderate. `final-production-audit.json`; residual full-tree/image scope remains open | +| Public CI on the final integration head | Required status checks, minimum-Node/Node 24 matrix and browser jobs; local passes do not substitute | **PENDING at this snapshot**. [PR #157 checks](https://github.com/GraphDone/GraphDone-Core/pull/157/checks) are authoritative for the final integration head after the documentation merge; an earlier-head run does not satisfy this row | +| Protected `dev` merge decision / resulting commit | Exact reviewed/tested head, required CI, human approval and remaining release blocks | **PENDING at this snapshot**. Record the final decision and resulting commit in [PR #157](https://github.com/GraphDone/GraphDone-Core/pull/157); scoped audit-branch merges above are not `dev` approval or deployment | + +Browser commands require `TEST_URL=http://127.0.0.1:13127`, the matching owned +API/database and installed lockfile Chromium. Run browser selections serially; +new-user, mobile and performance pin one worker and zero retries. Smoke's +inherited retries do not make a retried outcome acceptable. Preserve each output directory rather than +overwrite earlier evidence. A later documentation-only commit must remain +distinguishable from the functional source that was tested. + +The passing strict smoke and performance reports each recorded clean `e120df4` +at start and finish, with the same unchanged `5d8eb5cc…3f0636` source fingerprint +quoted for the new-user pass above. Their reports contain no evidence errors. +The standalone living-graph JSON does not embed Git provenance; its separate +provenance file records the command, exit status and clean `bdf82cc` checks +before and after execution. Do not conflate that attribution with the unified +runner's start/end source fingerprints. + +Artifact origins are distinct. These audit-workstation paths are relative to +`.worktrees/`, not interchangeable directories: + +| Evidence | Worktree and retained path | +| --- | --- | +| Original 36-pass/six-failure browser baseline | `core-validation-20261023/test-artifacts/audit-20261023/strict-new-user/` | +| Three-case measured performance pass | `core-perf-gate-20261023/test-artifacts/audit-20261023/perf-final*` | +| Other combined logs, JSON probes, audits and completed/running final browser reports named above | `core-final-candidate-20261023/test-artifacts/audit-20261023/` | + +Before final approval, attach public review or durable sanitized evidence links; +a local path alone is not published evidence. Custom diagnostic redaction is +bounded, not general content sanitization. CI trace artifacts use disposable +test fixtures and follow the workflow's upload policy; the raw ZIPs can still +contain authenticated headers/bodies. Neither disposable fixtures nor #176's +safe file-copy boundary establishes that artifact contents are sanitized. + +The one-off HTTP probe used two independently authenticated USER accounts and +owned folders/private graphs. It checked own-folder creation and own-graph +association, rejection of foreign graph association/folder mapping reads/folder +deletion, unchanged mapping after rejection, current stored demotion overriding +an older USER token, and synthetic-guest write rejection. These eight checks +exercise the actual HTTP/Neo4j/SQLite boundary. The `/tmp` script is not committed +or a CI-discovered test; keep its JSON and scope separate from repeatable folder +unit/schema regressions. It does not establish every generated resolver, +transport or deployed permission boundary. + +The two image probes used API image +`sha256:af10a0b89522d0ab92a335fafa38d345d2beceeb32ea9440899706fbe4884aca` +and generic image +`sha256:96ddc59160c4682f8fd324041a60cc4c4482447332354524d2e1ee61af0aa0a2`. +Both connected to the owned Neo4j fixture and passed authentication/readback; +their optional MCP service remained offline. The positive-probe containers were +stopped afterward. These are local image startup checks, not deployed health or +complete multi-service acceptance. + +The performance pass used four placed nodes/three edges and one real measured +tick per metric case, with a terminal matching-graph snapshot and unchanged +saved positions. A single pinned tick is not an FPS benchmark, a large-graph +capacity claim or evidence for unplaced layout performance. + +## Acceptance mapping and release blocks + +This is a mapping to evidence, not a PASS checklist. Unproven acceptance questions +remain **BLOCKED**, even if all Core merge gates eventually pass. + +| New-user questions | Core evidence being validated | Remaining blocked release scope | +| --- | --- | --- | +| 1–6: entry, guest, account | Guest/admin UI and bounded password/magic-link/startup tests | Real remote email delivery/recovery, sign-out/session lifecycle and provider failures | +| 7–10, 12, 24–34: navigation and modes | Owned same-work fixture across modes, visible mobile controls, graph isolation and modal dismissal | Every control/filter/route/role combination; hosted backend parity | +| 11: browser history and direct routes | Some navigation/reload paths are exercised | **BLOCKED:** complete Back/Forward, bookmarked-route and direct-route reload journeys are not established | +| 13–23: useful work and recovery | Create/edit/grow/readback/reload, delayed-save drafts, scoped undo and layout retry | Multi-actor conflicts, forced-close/crash persistence, full connection/reverse/delete matrix | +| 35, 54–62: agents | Recorded contributions, explicit refresh and persisted visibility | Autonomous execution; complete agent-token purpose/scope/revocation and external-provider journeys | +| 36–41, 53: comfort and graph motion | Browser geometry/focus, actual drag/framing, measured graph snapshots | Native mobile keyboards, screen readers, live sign-in main landmark, device rotation/zoom combinations, large/unplaced graph performance | +| 42–43: states and display preferences | Selected loading, error and saved-state boundaries have tests | **BLOCKED:** complete loading/empty/error/offline/success/disabled-state acceptance and high-contrast/reduced-motion journeys are not established | +| 44–52: online release | Core localhost/schema/build evidence plus the separate live entry-page check | Exact Pages artifact/assets/API routing, hosted D1 mutation parity, headers/secrets, migrations/rollback and live multi-account journeys | + +Security remains **BLOCKED for a comprehensive release-security claim**. The +reviewed folder/current-role, queue-owner, magic-link, password-accounting and +secret-startup fixes cover specific boundaries. Server-verified CAPTCHA policy, +login-versus-reset token purpose, broader session revocation and OAuth verified-email +linking remain open; remote provider flows were not exercised. In-memory password +budgets do not establish multi-replica enforcement. Residual dependencies and +untested permission/transport surfaces remain in the dated +[security scope](SECURITY_SCOPE_2026-10-23.md); its original findings are historical +and must be read alongside the follow-up PRs above. + +Use [RELEASE_QC.md](RELEASE_QC.md), [RUNBOOK.md](RUNBOOK.md) and the full +[NEW_USER_GATE.md](NEW_USER_GATE.md) for later release evidence. No unchecked +question, unavailable provider, local note or unmerged PR counts as delivered +production validation. diff --git a/docs/quality/DETAILS_SAVE_COMPLETION_2026-10-23.md b/docs/quality/DETAILS_SAVE_COMPLETION_2026-10-23.md new file mode 100644 index 00000000..acbb9f86 --- /dev/null +++ b/docs/quality/DETAILS_SAVE_COMPLETION_2026-10-23.md @@ -0,0 +1,104 @@ +# A save acknowledgement must preserve edits made after submission + +Date: 2026-10-23. Reproduction baseline: combined revision `aece679`. + +## Symptom and cause + +The strict new-user browser gate's 390px graph-details case saved a due date, +observed it in Core API readback, then cleared the date while the first save was +still awaiting query refetches. The first save's completion restored the date +and disabled Save. The failure screenshot is retained in the audit artifacts at +`strict-new-user/assets/new-user/1.png`. An isolated rerun of the unchanged 390px +case passed in 36.7 seconds; that does not negate the intermittent failure. + +`WorkItemDetailsModal` used one mutation-level `onCompleted` callback for both +save and undo. After `awaitRefetchQueries`, it replaced both `editedNode` and +`savedNode` with the earlier response. A guard on background `node` prop refresh +could not protect against that callback. Returning a draft to its original +saved value while a write was pending also made the draft appear clean, letting +the optimistic/cache refresh overwrite that deliberate edit. + +Independent review found a related history race: Workspace clears one reused +UndoStack when switching graphs. A pending save could complete afterward and +push the old graph's inverse into the new graph's cleared history. + +## Corrective rule + +Each write captures its submitted snapshot and editor session (owner, graph, +item, and opening). Its acknowledgement advances the saved baseline, then +merges only fields that have not changed since submission. Pending writes block +background replacement even when the current draft matches the older saved +baseline. A completion or failure from an earlier editor session cannot replace +the current form or show a stale notification. + +Undo keeps the confirmed save's previous snapshot and preserves fields edited +before or during the inverse request. UndoStack exposes a clear generation; +completion may register an inverse only if that generation still matches. +Workspace also clears history when the owner changes. Closing a modal without +clearing history still lets the successful in-flight save register its inverse. + +A completion must also confirm exactly the requested item before updating the +saved baseline, reporting success, or registering undo. Empty and wrong-item +results are rejected for save and undo. GraphQL response errors preserve the +newer draft and allow retry; undo rejection reaches its caller. The existing +UndoStack policy of removing a failed inverse is unchanged. The current Apollo +defaults use `errorPolicy: 'all'` for reads only; these tests retain the actual +default rejection policy for mutations. + +## Dated deterministic evidence + +On 2026-10-23 with Node 22.23.4 and a clean pinned `npm ci`: + +```text +npm test --workspace=@graphdone/web -- src/components/WorkItemDetailsModal.test.tsx + +Initial 11 completion/history tests with original aece679 modal and UndoStack: +9 failed, 2 passed + +Final tests with the fix: +18 passed + +npm test --workspace=@graphdone/web -- src/components/WorkItemDetailsModal.test.tsx src/lib/__tests__/undoStack.test.ts +25 passed + +npm test --workspace=@graphdone/web +326 passed across 40 files + +npm run build --workspace=@graphdone/core +npm run typecheck --workspace=@graphdone/web +npm run build --workspace=@graphdone/web +exit 0 (web build retains existing large-chunk warnings) +``` + +The tests render the real modal in React StrictMode with a real Apollo client, +cache, mutation hook, optimistic response and `awaitRefetchQueries`. An in-memory +link acknowledges the mutation and holds the edge refetch so a user can make a +newer edit in a deterministic completion window. It also tests reverting a +field before mutation acknowledgement, navigation to a different item, reopening +the same item, owner/graph changes, failure/retry, stale failure notifications, +two consecutive saves and inverse snapshots, editing during undo, cleared +history, and normal modal unmount followed by undo. + +The final suite also rejects GraphQL response errors, empty acknowledgements and +wrong-item acknowledgements for both save and undo. Before the acknowledgement +guard, those six cases produced four failures and two passes (GraphQL errors +already rejected correctly). Removing only the generation check makes the +unmounted/cleared-history regression fail with two history entries instead of +one, while the mounted context-switch case still passes. This negative control +proves the generation check independently of the graph/owner guard. + +The browser test's existing 16-second draft pause is not proof of a full polling +cycle: `GraphVisualization` polls at 60 seconds. Its misleading comment is +corrected; the controlled Apollo refetch regression establishes this specific +retention property without relying on elapsed time. + +## Limits + +This is a local draft/completion and undo-history fix. It does not add autosave, +durable drafts across page closure, or server-side conflict resolution between +different actors. The component fixture uses an in-memory link and mocked +account/graph/notification contexts; it is not a real API/browser test. The +parent's final combined headed gate must validate the fixed application; the +unchanged baseline's passing isolated rerun is recorded only as diagnostic +evidence. Apollo's existing `canonizeResults` development warnings remain visible +in the component-test output. diff --git a/docs/quality/DURABILITY_REVIEW_2026-10-23.md b/docs/quality/DURABILITY_REVIEW_2026-10-23.md new file mode 100644 index 00000000..01760830 --- /dev/null +++ b/docs/quality/DURABILITY_REVIEW_2026-10-23.md @@ -0,0 +1,30 @@ +# Pending saves must survive view navigation, and deletion needs settled connection evidence + +On 2026-10-23, independent review of the durability changes reproduced two gaps with the rendered React hooks and the real offline Apollo adapter. + +## Evidence + +- Before the deletion correction, `npm run test --workspace @graphdone/web -- src/lib/offline/link.test.ts` failed three new scenarios (5 passed): delayed edges, failed edges, and switching graphs while the next graph's edges were pending. With tasks `a`, `b`, `c` and edge `a → b`, the dialog offered all three tasks as unconnected before the edge query completed. The offline adapter then deleted the selected connected tasks too. +- Before the lifetime correction, the hook navigation regression failed with `expected spy to be called 2 times, but got 1 times`: after a failed save, unmounting Graph discarded the pending queue. Keeping the browser tab open did not preserve that component-local state. +- After correction, the focused adapter/modal tests pass (8/8), provider/hook lifecycle tests pass (8/8), and queue/auth-link tests pass (15/15), and stale-response ownership tests pass (3/3). Commands: `npm run test --workspace @graphdone/web -- src/lib/offline/link.test.ts src/hooks/__tests__/usePositionSaveQueue.test.ts src/lib/__tests__/positionSaveQueue.test.ts src/lib/__tests__/authLink.test.ts src/lib/__tests__/apolloErrors.test.ts src/lib/__tests__/authStorage.test.ts`. + +## Cause and corrective rule + +An empty connection map is not evidence that a task is unconnected. Destructive controls now require successful, settled task and connection results for the current query variables; loading, partial/error, and changed-graph results fail closed. Query results are the source of truth after mutations, and mutation completion awaits their refetch. + +An in-memory retry queue must live at least as long as the navigation it promises to survive. A provider above the routed application retains pending layouts by account and graph. Leaving a graph detaches its save callback; reopening that graph as the same authenticated account attaches a fresh callback and retries. Each save checks that the cached user matches the current token identity (Core `userId`/`id`, hosted `uid`, or a local guest), the mutation includes its graph filter, and the HTTP auth link rechecks the owner of the exact bearer snapshot before attaching it. A cross-tab storage-change regression failed before the single-snapshot check and passes afterward. A failed request for another owner cannot be preserved as the newly signed-in account's draft or clear that replacement session. Independent review reproduced the token transition before this guard: starting an owner-A mutation then calling `setToken` with owner B before the auth-context microtask resolved sent B's bearer while the cached user still identified A; the corrected regression rejects before transport. + +The provider retains at most 16 graph/account scopes and 10,000 distinct pending tasks per scope, counting in-flight work. It reclaims only inactive empty scopes. Capacity errors are explicit and never silently evict pending work. A browser-leave warning remains active while pending work exists, including in List view. Tests cover failure during navigation, newer edits during an older in-flight failure, graph/account isolation, logout during a bounded batch, token refresh, and capacity behavior. + +## Limits + +Pending layouts are tab-memory state, not a persistent outbox: a forced close, crash, or browser ignoring the unload warning can still lose them. While in List or another route, the queue waits for the original graph to reopen; the user-facing retry message says so. Already-issued requests can complete under their original account, but later requests cannot use a replacement account's credentials. These tests do not establish browser rendering, live-service health, cross-device conflict resolution, or backend atomic deletion protection; those require separate integration/release checks. + + +## 2026-10-28 follow-up + +The historical memory-only limit above is addressed by issue #181's +[network-backed layout recovery implementation and evidence](LAYOUT_RECOVERY_2026-10-28.md). +It reuses the existing account IndexedDB store, adds account/graph/node receipts +and bounded local generation fences, and keeps offline-sync durability separate. +The original navigation, owner/token and deletion regressions remain required. diff --git a/docs/quality/DYNAMIC_LOD_VALIDATION_2026-10-27.md b/docs/quality/DYNAMIC_LOD_VALIDATION_2026-10-27.md new file mode 100644 index 00000000..f8cb6a33 --- /dev/null +++ b/docs/quality/DYNAMIC_LOD_VALIDATION_2026-10-27.md @@ -0,0 +1,121 @@ +# Semantic overview validation — 2026-10-27 + +The first production-renderer slice adds readable overview groups and member +navigation. It does not complete the [dynamic LOD delivery plan](../architecture/dynamic-graph-lod.md). +CodeGraph supplied ideas; both projects keep independent implementations and no +shared library was introduced. + +## Revisions and environment + +- Base: `ef495eff15ed7aac51e72dc7a1db1bb96d2b3695`, also the live remote `dev` + when rechecked on 2026-10-27. +- Implementation: `df5d1cab932985092d77c17826af9d5cfc0e94b0`. +- Bidirectional readability refinement: `8ddd573c6edd433b9106a445b60e7c8b45009164`. +- Final validation/test-enrollment head: + `5f11f3bcc97fa3e90ffa1ea31f8e75f97a1ace16`. Its only application-source change + from `8ddd573` removes an unsupported lint-rule directive; the runtime behavior + is unchanged. Other changes enroll the browser tests in headed acceptance. +- Independent review found no blocking findings at `df5d1ca`; its two nonblocking + follow-ups were addressed in `8ddd573`, and subsequent code reviews had no + findings. This is not a GitHub non-author approval. +- Node 22.18.0, npm 10.8.2; clean `npm ci --no-audit --no-fund` installed 1,221 + packages. Dependencies and lockfile remain unchanged from the reviewed base. +- Chrome 151.0.7922.71, headed under Xvfb, one browser worker and zero retries. + Phone coverage uses a 390×900 viewport and CDP touch emulation, not phone hardware. +- Owned Neo4j 5.26.12 container, built API on port 28527, isolated web on 28528. + Tests never seeded or edited the owner's application on port 8540. + +## Results + +| Check | Result and revision | +| --- | --- | +| `npm run typecheck` | Pass, five tasks at `5f11f3b` | +| `npm run lint` | Pass, four tasks at `5f11f3b`; existing warnings remain | +| `npm run build` | Pass, four tasks at `5f11f3b` | +| `npm run perf:bundle` | Pass: largest gzipped JS chunk 265.8kB / 450kB budget | +| `npm run test -w @graphdone/web -- --maxWorkers=1 --minWorkers=1` | 335/335 across 41 files at `5f11f3b` | +| `node --test tests/lib/runner/unified-cli.test.mjs` | 25/25, zero skipped at `5f11f3b`; verifies LOD enrollment in the blocking new-user sequence | +| `semantic-lod.spec.ts` | 2/2 at `8ddd573`, 1440×900 and 390×900, 1.9 minutes | +| Existing `graph-motion`, `graph-resize`, `graph-details` specs | 7/7 at `df5d1ca`, 3.5 minutes; subsequent behavior changes only adjust the ≥40-item overview threshold, which these small-graph fixtures do not enter | +| `npm audit --omit=dev --audit-level=high` | **Fails**: 5 moderate / 1 high in the unchanged dependency tree; high finding is Undici 6.28.1. No dependency upgrade was folded into this feature. | + +Run the focused browser suite with an owned stack and installed browser: + +```sh +TEST_URL=http://127.0.0.1:28528 QC_BROWSER_PORT=28529 \ + xvfb-run --auto-servernum npm run qc:lod +``` + +The two browser journeys use **301 pinned items and 370 relationships**, with +eight explicit top-level groups and one long multiline title. This matches the +reviewed localhost graph's cardinality, not its private topology or content. +Assertions cover: + +- 14px non-overlapping summary titles, complete group/member access including + phone overflow, and the original source item in the inspector. +- Parent coverage while zooming in through 1.1; item titles ≥12px at 1.2, then + while zooming out through 1.14 and 1.01; summaries return at 0.99. +- Real wheel input and mouse pan over summary cards; real CDP touch pan/pinch; + keyboard open/Escape, inspection, Back, and focus restoration. +- Forced Overview at normal item scale hides source groups and arrows from + painting/hit testing; source filters remain in D3-owned definitions. +- The same SVG survives representation changes within a graph. The existing app + may replace it when changing graphs. A→B→A starts a fresh inspection session + and Back restores the new entry camera exactly. +- Zero attempted GraphQL mutations during guarded navigation; exact source + positions and IDs before/after navigation and reload; zero page errors. + +The pure projection tests additionally conserve every directed, typed edge, +including parallel/reverse edges, internal links, self-loops and missing endpoints; +they exercise native containment, cycles, conflicting parents, malformed metadata, +stable membership and untouched canonical objects. + +## Screenshots + +These are synthetic owned fixtures captured from the production component. + +![Desktop semantic overview](images/dynamic-lod-20261027-desktop.png) + +![Phone semantic overview](images/dynamic-lod-20261027-phone.png) + +## Failures retained and corrections + +Local raw evidence remains in the owned worktree's ignored +`test-artifacts/lod-evidence/`. The records include: + +1. A watched API restarted during a concurrent Core rebuild, and 100-input + Neo4j create batches exceeded fixture setup time. Use the stable built API + and repeated 20-input batches; retain the full 301/370 dataset. +2. Member selection moved the camera but lost the inspector when the detached + click target reached the document outside-click handler. The overview stops + click propagation; both viewport regressions subsequently passed. +3. A default-camera refresh on startup could prevent initial fit. Refresh title + geometry only when leaving Overview in the same graph scope. +4. The graph-switch test initially assumed its selector closed automatically; + the existing selector intentionally stays open. The test now closes it with + Escape. A later assertion incorrectly demanded the same SVG across different + graphs; identity is now checked within each graph's LOD navigation session. +5. The unconstrained parallel unit run had a 16ms timing assertion fail and a + 5s UI timeout under load. Sequential workers passed all 335 tests, with the + original failures retained. +6. Final lint rejected a suppression for a plugin rule this repo does not load. + Removing that directive fixed lint without changing runtime behavior. + +See the [renderer-ownership lesson](../../.agents/inbox/2026-10-27-lod-projections-need-renderer-ownership.md) +for causes and corrective rules. + +## Cleanup and remaining gates + +The owned API/web processes, Neo4j container and anonymous volumes were removed. +Owned SQLite authentication/operations databases were removed after process exit. +The owner's original localhost app still returned HTTP 200 afterward. Other +worktrees, CodeGraph source, main, deployments and repository protections were +unchanged by this work. + +This is not merge or release readiness: the production audit is red, full remote +branch CI and broader integration/image/installer gates have not been established, +and no deployment has been performed. This slice still constructs the complete +source SVG and loads full graph snapshots. Nested spatial expansion, local density +budgets, summary-first loading, Cloud parity and measured 1k/10k capacity remain +on the delivery plan. Existing autosave of newly laid-out/overlapping source nodes +is not fixed by the zero-write test on pinned, non-overlapping fixtures. diff --git a/docs/quality/LAYOUT_RECOVERY_2026-10-28.md b/docs/quality/LAYOUT_RECOVERY_2026-10-28.md new file mode 100644 index 00000000..23697c5e --- /dev/null +++ b/docs/quality/LAYOUT_RECOVERY_2026-10-28.md @@ -0,0 +1,139 @@ +# Pending layout recovery across reload and browser process loss + +Issue #181 extends the existing navigation-safe position queue. The underlying +Core source starts at reviewed `525ca4e`; integrated dev `780a759` has the same +complete tree. Local preparatory results below used the isolated working tree, +not unchanged `525ca4e`. The containing PR's exact head, independent review and +final CI determine integration eligibility. + +## Supported behavior + +In network-backed mode, dragging queues the newest coordinates per owner, +graph and node. The existing `graphdone-offline` account store commits the +receipt in a strict IndexedDB transaction before the queue sends it. The UI +separately reports positions retained only in this tab, positions committed on +this device, pending sends, and server acceptance. A known server acceptance +followed by failed receipt cleanup remains accepted; retry performs cleanup +without sending the coordinates again. + +The queue hydrates before graph initialization. A delayed or failed initial +storage read cannot render an older server layout and silently ignore recovered +coordinates. Switching owner/graph stops the previous simulation and checks its +scope at save callbacks. Pending position snapshots and their work-item overlay +retain identity across camera renders, preserving LOD projection and label +placement memoization. + +Leaving Graph for Work list detaches the callback but retains the queue. Reload +or browser restart recovers committed receipts when the same account and graph +return. Each actual mutation still checks the current owner, exact bearer +snapshot and graph filter. A rejected or empty mutation result retains work. +Logout and another account cannot replay the original account's positions. + +Settings → Pending layouts lists this account's pending graph scopes, including +graphs that are no longer accessible or have been deleted. Discard requires an +explicit confirmation there. In the active graph, Retry and Discard unsent +layout operate on its scoped queue. Discard never needs to persist a memory-only +edit first, and reconnect cannot send while its discard is in progress. +Already-issued server requests cannot be recalled by discarding local work. + +In `VITE_OFFLINE_SYNC=true` mode, the existing local project mutation and sync +engine remain the authority. Successful local mutation means saved on this +device, while upload/conflict status remains in the existing sync UI. This +change adds no second layout outbox in that mode. Network receipts and their +generation fences are excluded from portable offline backups. + +## Ordering, retention and limits + +- A fresh receipt check, send and cleanup run under a Web Lock keyed by owner, + graph and node. Missing Web Locks are explicit and prevent uncoordinated + network replay. Missing/unusable IndexedDB or quota failures also remain + explicit; no durable or server-saved status is invented. +- Persistence compares the batch's original observed graph generation inside + its account transaction. A stale volatile edit cannot write itself back after + another tab saves. Conflicting edits remain in the tab with a visible message; + discard and reload to adopt the newer layout. Reads never silently rebase an + already-staged edit. This conservatively rejects simultaneous edits on + different nodes in the same graph too. +- ACK removes only the matching receipt revision. Explicit durable discard also + advances its graph fence. Account counters and at most 16 recent graph fences + survive the last receipt's removal. An eviction advances a retained watermark to the new account counter; + absent graphs fall back to that watermark, preventing reuse of an older + generation. Unrelated graph saves without eviction leave that fallback stable. Eviction can cause + a conservative conflict after unrelated graph activity. Invalid or exhausted + safe-integer counters fail explicitly. +- Persistent pending work is bounded to 16 graphs and 10,000 positions per + account. The provider retains at most 16 account/graph scopes and the existing + per-scope queue bound. Capacity rejection preserves earlier work. There is no + automatic receipt expiry; ACK or explicit discard removes it. +- Only IDs, finite bounded coordinates, timestamps and revision metadata are + stored by this feature. It stores no bearer/session credentials. It does not + encrypt browser data or survive clearing/eviction of the browser's storage. +- SIGKILL acceptance proves recovery after this owned browser process group + terminates, following a completed IndexedDB transaction. It does not prove + recovery after power loss, hardware failure or an operating-system storage + failure. Edits still labelled only-in-tab can be lost on termination. +- A crash or lost response after the server has applied a write but before its + receipt is removed is an ambiguous at-least-once boundary. In-flight requests + may also finish after a browser dies. Server idempotency/revision ordering and + multi-device conflict resolution are outside #181; this is not exactly-once + replay or a guarantee against later writes by another actor. + +## Regression evidence + +The dated, owned local evidence directory is +`/tmp/graphdone-resolution-20261027`. It is not a published artifact or a +substitute for the repository regressions and PR checks. + +| Boundary | Retained negative/control and corrective evidence | +| --- | --- | +| Provider loss | Original provider-replacement test failed with zero recovered sends; corrected provider recovers the newest coordinates. | +| Receipt and discard ordering | Seven ordering cases failed before correction. Tests cover stale durable replay, accepted-save cleanup, quota-only discard, pending discard versus flush, same-item serialization, unsupported locks and stable snapshot identity. | +| Volatile stale replay | Matching bundled probe changed server `20 → 10`; the added regression failed before generation CAS. The actual two-tab/quota browser test now preserves the newer real server save. | +| Renderer scope | Held-IDB A→B switch plus pagehide raised erroneous layout attention before simulation ownership cleanup. The corrected browser test passes with no wrong-scope receipt. | +| Process ownership | First process-loss run failed profile cleanup; owned process-group termination fixes the writer lifetime. A second failed fixture assertion exposed the missing restart viewport, then explicit viewport restoration fixed it. | +| Fence isolation | A fresh graph incorrectly conflicted after an unrelated graph saved. `layout-fence-unrelated-negative.log` retains the failure. Advancing the fallback only on actual eviction preserves independent graph edits while retaining stale/evicted fence rejection. | +| Pending-layout LOD | Reintroducing a fresh item-array identity moves overview labels between slots during a real pan (including a 96px row change). `layout-lod-identity-negative.log` retains the expected failure. The corrected memoized source passes the same camera/label/zero-write assertions. | +| LOD fixture setup | First pending-layout probe passed camera translation instead of graph coordinates to the minimap hook. The corrected probe then wheeled over the minimap after panning. Both failures are retained; the final fixture targets an overview card explicitly. | + +The strict eight-case browser run in `layout-recovery-strict-first/report.json` +passed 8, failed 0, warned 0 and skipped 0, with exit 0, no timeout or evidence +errors. It uses real owned API/Neo4j data and IndexedDB: two real drags/reload +before ACK, delayed hydration, owned process restart, graph switching, actual +two-tab quota ordering, account isolation, revoked writes and deleted nodes/ +graphs, Settings discard, accepted-response cleanup, and unavailable storage/ +coordination. The pending-layout LOD case passes separately, and its deliberate identity-regression negative fails on changed label slots. All nine cases subsequently passed, but that run was rejected by the source-integrity gate because staging changed the repository fingerprint during execution. It is retained as failed evidence, not final acceptance; a committed, frozen-tree run is required. + +The local full coverage invocation passed 739 tests; 26 existing database +integration cases remain explicitly separate, not counted as passes. All 193 +harness tests passed. Build, types and lint passed; lint retains the existing +warning inventory and build retains its chunk-size warning. These numbers do +not establish comprehensive statement/branch coverage, hosted D1 parity or +release readiness. + +Run the browser gate against an owned Core stack, with explicit Neo4j fixture +credentials (the access-revocation test changes only its newly created graph): + +```sh +TEST_URL=http://localhost:3127 xvfb-run --auto-servernum \ + node tests/run-unified.mjs --sequence layout-recovery --out test-artifacts/layout-recovery +``` + +The sequence enforces one worker, zero retries and failure-only trace retention; +CI runs it in the existing Smoke Gate. Its fixture registers graph IDs at +creation, restores any temporary ownership change, deletes edges before nodes, +and verifies graph removal. The process-loss case owns and removes only its +temporary browser profile. Local Chrome and CI Chromium are separate browser +builds; neither is evidence for all engines, real phones or hosted D1. + +The frozen `c4df01e1567af10b6c4b0dc9d666ad946c1e0b75` run passed all nine +browser cases in 266.9 seconds, with exit 0, no timeout/evidence errors, and clean +matching start/end fingerprints. Its full unit invocation passed 740 tests +(with the same 26 gated database skips), and 193 harness tests passed. Build, +types and lint passed. Independent review then found a separate delayed Settings +discard/account-switch race; that candidate was not cleared for integration. +The new hook regression failed before correction and all 43 focused tests pass +after it. See the [account-label lesson](../../.agents/inbox/2026-10-28-async-errors-must-retain-account-ownership.md). +The containing corrected commit still requires renewed exact-head gates. + +The existing smoke Graph→List→Graph/ACK/reload case, auth-link token checks, +offline project transfer, semantic LOD and motion gates remain required. diff --git a/docs/quality/LAYOUT_RECOVERY_BROWSER_2026-10-23.md b/docs/quality/LAYOUT_RECOVERY_BROWSER_2026-10-23.md new file mode 100644 index 00000000..3cb01b28 --- /dev/null +++ b/docs/quality/LAYOUT_RECOVERY_BROWSER_2026-10-23.md @@ -0,0 +1,23 @@ +# Layout recovery needs a real navigation and server-readback regression + +On 2026-10-23, the position queue fixes received a headed browser regression in `tests/e2e/smoke/user-smoke.spec.ts`. The existing `test:smoke`, unified smoke, and new-user gate include that file, so this case participates in the normal application gates. + +The case creates its own graph, two tasks, and one edge through the real authenticated Core API. The graph ID is registered for strict edge-first cleanup immediately after creation. It holds only the first `UpdateWorkItems` request whose node and graph IDs match the fixture and whose update contains exactly `positionX` and `positionY`. All other requests continue normally. While that save is in flight, a second real mouse drag queues newer coordinates. The first request then receives exactly one HTTP 200 GraphQL error response. + +The test selects Work list through the application’s view control, proves that the graph canvas unmounted, and reads the original coordinates from the API while no further layout request occurs. Returning to Graph must issue the newer coordinates, and the API and reloaded canvas must agree. This exercises the actual caller, Apollo response handling, provider lifetime, and server persistence beyond the rendered-hook tests. + +## Recorded evidence + +Command (Node 22.23.4, existing owned Core/Neo4j validation stack): + +```sh +TEST_URL=http://127.0.0.1:13127 PLAYWRIGHT_BROWSERS_PATH=/tmp/graphdone-audit-playwright xvfb-run --auto-servernum npx playwright test tests/e2e/smoke/user-smoke.spec.ts --project=GraphDone-Core/dev-neo4j/chromium --grep 'failed layout save' --headed --workers=1 --retries=0 --trace=on +``` + +The focused run passed 1/1 in 13.3 seconds, with no retries. Its attached `layout-save-recovery.json` recorded original coordinates `(-240, 0)`, failed first-drag coordinates `(-180, 36)`, latest coordinates `(-120, 72)`, exactly two matching mutation attempts, exactly one injected failure, and server readback `(-120, 72)`. Reload and fixture cleanup assertions passed. + +The first test-development run did not reach the failure scenario: its pointer coordinates were outside the viewport before camera framing completed, and its `/api/graphql` route missed the app’s direct `/graphql` transport. The corrected case waits for the owned card to be fully visible and stable, and matches both endpoint forms while retaining the exact layout-mutation predicate. A quiet simulation alone does not establish that the camera is ready for a real drag. + +## Limits + +This case tests one graph/account and a controlled GraphQL save failure. Account-switch/token races remain covered by separate unit and Apollo-link tests. It does not establish crash persistence, recovery after forcibly closing the tab, a genuine network disconnect, or hosted backend behavior. The focused pass is not a substitute for the combined application gate. diff --git a/docs/quality/MAGIC_LINK_REPLAY_2026-10-23.md b/docs/quality/MAGIC_LINK_REPLAY_2026-10-23.md new file mode 100644 index 00000000..d1f98e62 --- /dev/null +++ b/docs/quality/MAGIC_LINK_REPLAY_2026-10-23.md @@ -0,0 +1,31 @@ +# A single-use email token must be consumed before returning identity + +On 2026-10-23, the shared SQLite magic-link verifier accepted one token more than once under concurrent requests. Its initial unused-token lookup was followed by an unguarded, unawaited update. Multiple requests could all finish the lookup before any update completed. The verifier also returned identity for an inactive account. + +## Correction + +The verifier now awaits an update guarded by token identity, unused state, and SQLite's current time when the statement executes. Only an update affecting exactly one row may continue to account lookup or creation. Other contenders return `{ valid: false }`. An inactive account also returns false, and its token remains consumed. Existing successful callers still receive `{ valid: true, email, userId }`; a fresh link still creates a new account, and existing accounts can redeem independent valid links. + +A database error during consumption rejects before returning identity. If the update failed, the unused token can be retried after the database problem is repaired. If consumption succeeded but the later account lookup fails, the token remains used; the user must request a fresh link. This is intentional failure behavior, not a transaction spanning account creation and token consumption. + +## Evidence + +Base: `5f0f454`. Tests use the actual SQLite store with fresh in-memory databases; no shared database, runtime port, email delivery service, or deployed credential is involved. + +Before correction: + +```sh +npm run test --workspace @graphdone/server -- tests/magic-link.test.ts -t 'existing account|inactive account' +``` + +The selected controls produced **2 failures, 1 pass**, with seven unrelated cases filtered out. Twelve concurrent redemptions of one existing account's token returned multiple successful identities. The inactive-account case returned a successful identity as well. + +Self-review of the first fix (`4f4b782`) found that binding a JavaScript timestamp before scheduling the write could accept a token whose expiry passes before SQLite executes it. A controlled write-delay regression failed against that revision. It holds the consumption call, lets the actual SQLite token expire, confirms expiry using SQLite's clock, and releases the real write. Moving the expiry check and consumption timestamp into the SQL statement makes the case pass without consuming the expired token. + +After correction, `npm run test --workspace @graphdone/server` passed **39/39**, including all **11 magic-link cases**. Coverage includes concurrent existing/new accounts, distinct valid tokens, expired/used/missing tokens, expiry during a delayed write, inactive accounts and reactivation, missing-table lookup errors, a real SQLite trigger that rejects the consumption update, and an account-lookup failure after consumption. The existing server suite passes too. Server typecheck/build and whitespace checks pass; ESLint reports zero errors with existing legacy warnings. + +Local evidence: `/tmp/graphdone-magic-link-red.log`, `/tmp/graphdone-magic-link-green.log`, `/tmp/graphdone-magic-link-expiry-red.log`, `/tmp/graphdone-magic-link-expiry-green.log`, `/tmp/graphdone-magic-link-typecheck.log`, and `/tmp/graphdone-magic-link-server-build.log`. Public PR checks and independent review provide subsequent commit-specific evidence. + +## Remaining scope + +Login and password reset still share the same token purpose; purpose separation needs its own change and cross-purpose tests. This patch does not establish OAuth linking, CAPTCHA verification, password-login throttling, signing-secret configuration, general session revocation, or live email/HTTP behavior. Browser and integrated service gates remain separate from these store-level concurrency tests. diff --git a/docs/quality/MCP_CPU_TESTING_2026-10-23.md b/docs/quality/MCP_CPU_TESTING_2026-10-23.md new file mode 100644 index 00000000..773b357a --- /dev/null +++ b/docs/quality/MCP_CPU_TESTING_2026-10-23.md @@ -0,0 +1,21 @@ +# Graph unit tests must control CPU samples without disabling CPU protection + +On 2026-10-23, the combined validation run failed three MCP GraphService cases with `CPU exhaustion protection activated` while the host was busy. The default tests used a mock Neo4j driver but still sampled the host's real `os.cpus()` counters. Local test mode raises the threshold to 95%; it does not remove the guard. A short burst can therefore block unrelated graph assertions. + +## Reproduction and correction + +A temporary Vitest setup supplied monotonically increasing CPU counters with 100% busy time to the unchanged `tests/mcp-server.test.ts`. On Node 22.23.4 this reproduced 9 failed / 8 passed cases. The first rejection can report `0.0% avg, 0.0% peak`: the existing trend formatter returns zeros until it has five samples, even though individual samples already blocked the operation. Zero trend output is not evidence that throttling did not run. + +The three mock-database GraphService unit suites now import `tests/stable-cpu.ts`, which supplies deterministic idle CPU deltas. They still execute the real CPU monitor, stress check, throttling wrapper, and graph service. No production setting, environment bypass, threshold, or database-backed suite was changed. The same external busy-sample reproduction then passed all 17 original cases. + +The separate `tests/cpu-monitor.test.ts` controls CPU deltas, the monitor clock, and timers. Each case explicitly selects production thresholds. Seven tests verify the 80% guard, bounded retries under sustained load, recovery within the retry budget, the operation rate window, sustained-load detection after the instantaneous load drops, GraphService rejection before opening a database session, and no replay after an operation itself fails. Timer/module state is reset between cases. + +## Validation and limits + +Commands used Node 22.23.4 and an isolated lockfile install: + +- `npm run test --workspace=@graphdone/mcp-server -- tests/mcp-server.test.ts tests/graph-operations.test.ts tests/graph-context.test.ts tests/cpu-monitor.test.ts`: 58 passed. +- `npm run test --workspace=@graphdone/mcp-server`: 97 passed, 25 database-gated cases skipped (7 files passed, 3 skipped). +- `npm run typecheck --workspace=@graphdone/mcp-server`: passed. + +The skipped cases were not counted as passes, and no shared database was accessed. These tests establish deterministic behavior under specified samples, not operating-system sampling accuracy or load-test capacity. The existing trend-message behavior and automatic CI/test-mode selection remain separate production-policy concerns. diff --git a/docs/quality/NESTED_LOD_VALIDATION_2026-10-28.md b/docs/quality/NESTED_LOD_VALIDATION_2026-10-28.md new file mode 100644 index 00000000..32aec7bc --- /dev/null +++ b/docs/quality/NESTED_LOD_VALIDATION_2026-10-28.md @@ -0,0 +1,136 @@ +# Nested semantic LOD validation — 2026-10-28 + +Issue #215 continues the [delivery plan](../architecture/dynamic-graph-lod.md#9-nested-hierarchy-continuation-2026-10-28-215). +This ledger describes the candidate before final independent review and CI; +exact accepted revisions and final results belong in the implementation PR. + +## Source and environment + +The candidate starts at `53c124c39fd0e8495f26e27a981ad06557805878`, whose full tree +`fe1a54bc68148a2c5955c0d350784218c348d5f6` equals native dev merge +`77899e98a081e725ae8ddc15b6566cc50c9a584f`. It includes #213 layout recovery and +#214 self-host maintenance. Node 22.18.0/npm 10.8.2 clean install and Core build +succeeded. Owned Vite port 28928 uses the stable built API on 28817 and disposable +Neo4j 5.26.12 on bolt 28687. Original localhost 8540 and CodeGraph remain untouched. + +Browser coverage uses installed Chromium, one worker, zero retries, a desktop +1440×900 viewport and a 390×900 viewport with actual CDP touch events. This is +phone emulation, not physical-device acceptance. Fixtures register owned IDs +before setup completes and delete edges before nodes; teardown errors fail tests. + +## Failing-before and preliminary evidence + +On 2026-10-28, two new hierarchy assertions failed against the previous projection +(wrapper ignored the requested cut): explicit nested/direct membership and nested +native containment. The candidate passed those assertions and then all 18 model +cases, including unique coverage at every cut for 200 items/503 relationships, +reordered input, source object identity, cycles, parallel/opposite/self links, +missing endpoints, 2,000-deep native containment, 100-segment explicit paths, +invalid metadata, deterministic IDs, hysteresis and label-budget parent fallback. + +The new desktop journey also ran against the unchanged `168b984` web source on +28818. It failed at the missing `Show 2 subgroups` control with the existing 10s +action timeout, after authenticating and opening the real group browser. Trace +and screenshot were retained; the owned fixture was cleaned. This is an expected +negative control, not an accepted test run. + +The prototype's first desktop/phone run passed 2/2 in 2.3 minutes, using 72 pinned +items and 111 typed relationships with three-level paths, direct parent items, +cross-system cycles, parallel edges and a long member title. It checked expansion, +ancestor collapse, source inspection, exact return camera, actual pan and desktop +wheel input, unchanged label slots, exact source readback and zero attempted +mutations/page errors. Later assertions add explicit selected-parent accessibility +and repeated zoom-threshold crossings; they require fresh validation. + +Retained local evidence is under `/tmp/graphdone-resolution-20261027/`: +`nested-lod-negative.log`, `nested-lod-old-negative.log`, +`nested-lod-old-negative-artifacts/`, `nested-lod-budget-model.log`, +`nested-lod-first-browser.log` and `nested-lod-first-artifacts/`. +The strict `nested-lod` sequence is enrolled in PR/full profiles and the CI Smoke +Gate, with zero retries and retained failure traces. CLI tests verify enrollment. + +## Review corrections + +Initial frozen candidate `43ada01` passed strict nested 2/2 in 86.5s with clean +matching source fingerprints; full unit coverage 750 passed plus 26 existing +DB-gated skips, 194 harness cases, types, lint and build also passed. Independent +review nonetheless found two P2s: Auto Back lost the entry hysteresis history, +and automatic replacement of a focused summary left focus on the document body. +The independent randomized model probe passed 1,200 fixtures / 6,000 cuts; these +UI findings demonstrate why pure conservation is insufficient. + +Four new component regressions failed before correction (depth return, visibility +return, focused expansion/collapse, live removal); the unrelated-focus control +passed. The actual desktop browser restored the exact camera but failed the old +root-group count after Auto Back. Both negatives and their traces are retained. +The correction saves/restores both Auto state machines and reconciles only lost +owned summary focus to a surviving member representative or the group list. +Focused corrected component/model tests passed 23/23, and types passed. See the +[canonical lesson](../../.agents/inbox/2026-10-28-lod-return-needs-hysteresis-and-focus.md). +The initial source's green checks do not validate this correction; fresh frozen +browser, full checks and renewed independent review are required. + +## Retained validation failures after review correction + +Corrected frozen `1baac30` passed strict nested 2/2 in 103.0s and renewed +independent review/probes. A concurrent full-coverage run exceeded the existing +5s limit in the new 40-member component fixture (392 other web cases passed). +The component test needs only a parent and two nonempty children, so it now uses +four members with the same five assertions. In an isolated clean-installed +worktree, the reduced fixture still failed all four intended assertions against +43ada01 and passed all five against the corrected component with coverage. The +first attempted isolated run failed before test execution because a symlinked +install could not resolve the nested Vite plugin; that startup error is retained +separately and is not counted as the executed negative. + +The existing 301/370 desktop browser journey also exceeded its unchanged 120s +interaction budget during the concurrent coverage workload; the phone case +passed. Its trace shows generally slow actions and clock advancement (one150ms +clock advance took6.98s), rather than an assertion for changed semantic behavior. +Source fingerprints remained clean/identical, and owned teardown completed. +This run is rejected, not retried into a pass. Final local validation serializes +CPU-heavy coverage and browser gates to remove this observed interference. +No 72-item/301-item browser fixture, assertion, timeout or retry policy is reduced. +If serial acceptance still fails, it remains a blocker requiring investigation. + +Evidence: `nested-1baac30-coverage.log`, `nested-existing-lod-1baac30/`, +`nested-small-fixture-negative.log` (startup failure), +`nested-small-fixture-executed-negative.log` (four intended failures) and +`nested-small-fixture-positive.log` (five passes with coverage), all under the +retained evidence directory above. + +## Initial-load readiness correction + +Frozen1874acb passed full755 unit cases (plus26 existing DB-gated skips),194 +harness cases, types/lint/build with the smaller component fixture. Its strict +nested run then failed the first desktop72-node assertion while phone passed. +The source stayed clean/identical. Retained trace shows app queries beginning +1.35s after navigation returned, then taking1.40s/1.92s; the screenshot after the +assertion deadline shows the loaded graph. The8.44s query elsewhere in that trace +was fixture edge creation, not this initial graph read, and is not cited as the +startup cause. + +The test now awaits the actual owned graph's GetWorkItems and full GetEdges +responses, checks HTTP/GraphQL success and exact source IDs against the seeded +snapshot, then applies the existing5s72-node rendering assertion. It uses the +normal bounded Playwright response wait inside the unchanged150s journey budget. +This clarifies the data-readiness boundary; it does not establish a first-paint +performance budget or improve application startup. No browser fixture, navigation +assertion, existing timeout or retry count changes. Retained failure: +`nested-lod-1874acb/`; fresh acceptance is still required. + +## Acceptance still required at the frozen candidate + +Run strict nested, existing 301/370 semantic LOD, layout recovery and smoke browser +sequences; existing mobile/new-user/performance gates; full unit/harness, build, +types, lint, audit, runtime images, real database contracts and installer gates. +Obtain independent exact-source review and inspect actual uploaded CI reports. +A passing prototype does not substitute for these gates or final dev CI. + +## Capability limits + +This adds nested display hierarchy and a screen-slot budget, not per-region +spatial virtualization or summary-first loading. All source SVGs/data remain +loaded. No 1k/10k capacity, hosted parity, topology clustering, production +publication or deployment is claimed. Existing source relationships and layout +recovery remain authoritative; summaries cannot be persisted as WorkItems. diff --git a/docs/quality/NEW_USER_GATE.md b/docs/quality/NEW_USER_GATE.md new file mode 100644 index 00000000..6bc96b1c --- /dev/null +++ b/docs/quality/NEW_USER_GATE.md @@ -0,0 +1,117 @@ +# The “dumb questions” quality gate + +These are product acceptance questions, not questions the user has to answer. Ask them at every QC gate from a fresh user's perspective. A screenshot or a successful HTTP response alone is not a pass. + +For every question record **PASS**, **FAIL**, **BLOCKED**, or **NOT APPLICABLE**, with the exact build, backend, viewport/input mode, reproduction steps and evidence. Unchecked questions remain BLOCKED. A failure in a primary journey blocks release; optional unsupported features must clearly say so and must not appear to work. + +## First visit + +1. What is this, and what can I accomplish here? +2. Can I try it without creating an account? +3. What can a guest actually do, and will my work disappear? +4. Why am I being asked to do this extra step? Can it be removed? +5. Can I sign in, recover access, sign out, and tell which account I am using? +6. If an account or provider is unavailable, does the app explain it honestly? + +## Finding my way + +7. Where am I, which graph am I changing, and how do I get home? +8. Can I switch graphs without showing or editing the previous graph's work? +9. Can I reach every view on my phone without knowing a keyboard shortcut? +10. Can I return from a detail, menu, or dialog with one obvious action? +11. Does browser Back, Forward, a bookmarked route, and a direct reload work? +12. Is the same navigation predictable in portrait, landscape and desktop? + +## Doing useful work + +13. Can I create my first work item from an empty graph? +14. Can I give it a useful title without filling out a form full of optional fields? +15. Can I open and edit it in one tap/click? Does Enter work on keyboard targets? +16. How do I change status, priority, description, tags, and due date? +17. Did it really save? Is the result still there after reload and in another view? +18. Can I add a progress note, read it later, and distinguish it from verified history? +19. How do I connect existing work or grow a connected item? +20. How do I fix a wrong connection, reverse it, or remove it? +21. Can I undo an accidental change? Is destructive work clearly identified? +22. What happens if saving fails or the network disappears? Can I retry without losing text or duplicating work? +23. What is the fewest number of actions needed to achieve this outcome? Are we making the user do bookkeeping the system already knows? + +## Every mode, using the same saved fixture + +24. **Graph:** Can I see, frame, select, edit, and connect the real work? Are small controls usable at this zoom? +25. **List:** Can I find and open work without reading every badge? +26. **Overview:** What is happening, what needs attention, and can I open that work directly? +27. **Table:** Are fields understandable, reachable, and consistent with saved data? +28. **Board:** Is actual work visible before empty lanes? Can I open a card and move its status? +29. **Timeline:** Are dates real? What does an unscheduled item mean? Are zoom and navigation useful? +30. **Calendar:** Does scheduled work appear on the correct day? Can I navigate dates and open an item? +31. **Activity:** What actually changed? Is this recorded data, with no fabricated events or authors? +32. **Analytics:** Do totals and filters match this graph, including empty/error states? +33. **Ontology:** What do types and connections mean? Are usage and requirement coverage real? +34. **Settings/Admin:** Does every offered action persist? Are unavailable controls honest and role restrictions enforced by the server? +35. **AI:** What is an agent actually doing? Is a contribution record clearly distinguished from a running, verified agent? + +## Comfort and accessibility + +36. Is the primary work visible before summaries, empty sections, and configuration? +37. Can I read labels and identify actions without interpreting colors or hover alone? +38. Can I use touch, keyboard, and focus navigation without getting trapped or losing my place? +39. Why is there a scrollbar? Is content genuinely too large, or did we create a redundant/nested scroll region? +40. Can I reach all content without clipping, horizontal page overflow, or hiding essential controls? +41. Does an open dialog still fit after rotation, zoom, or opening the mobile keyboard? +42. Are loading, empty, error, offline, success and disabled states distinguishable? +43. Do reduced-motion and high-contrast preferences preserve a usable interface? + +## Online release + +44. Does this exact static build load through Cloudflare Pages, including deep links and hashed assets? +45. Is it calling the intended API rather than localhost or a different environment? +46. Do create/edit/connect/delete and all views work against D1, not only the development database? +47. Can one account read or change another account's private graph? +48. Are dates, tags, metadata, relationships and supported settings retained through the hosted API? +49. Do refresh/reconnect and expired credentials recover clearly? +50. Are secrets absent from browser assets and are cache/security headers correct? +51. Can we identify the released source, apply migrations, roll back the frontend, and see failures and usage? +52. Are unsupported advanced managed capabilities clearly separate from the free Core? + +53. When I stop dragging or editing, do nodes and labels stop shaking? Does refresh or rotation unexpectedly move work I placed? Does the camera keep work centered when the window grows as well as shrinks, including gradual changes? + +## Required gate procedure + +- Run `TEST_URL=http://localhost:3127 npm run qc:new-user` against Core with the local app/API available. Browser checks are headed and serial by default. On Linux use a running DISPLAY, or `xvfb-run --auto-servernum` in CI; install Chromium with `npx playwright install chromium` first. The CI smoke job and PR template both require this gate. +- Run the same all-mode fixture against the Cloud-configured frontend and local D1 API with `QC_BACKEND=cloud QC_API_URL=http://localhost:8797/api/graphql TEST_URL=http://localhost:3129 npm run qc:modes`. +- Inspect screenshots as well as assertions. The all-mode test checks the same saved work through actual view navigation; it does not replace mutation, authorization, error-recovery or accessibility tests. +- Before deployment, validate the staged production Pages artifact, record the cloud integration results and finish the release ledger. No skipped test or unavailable provider counts as a pass. +- Cover 320/360/390px phones, short landscape, tablets, laptop and desktop. Native mobile keyboards, screen readers and remote account/email flows require explicit evidence and cannot be inferred from desktop browser emulation. + +Use `SCENARIOS.md` for the diverse user/team catalog and `SCENARIO_RESULT_TEMPLATE.md` for consistent results. + +See `RELEASE_QC.md` for the current sweep and remaining blockers. + +### Saved-state and recorded-AI checks + +The responsive editing test waits for the exact status mutation, rejects HTTP or +GraphQL failures, checks its saved-state indicator, and reads the item back from +the API before navigating to Analytics. The 320px case delays that request by +750 ms to expose navigation before the save acknowledgement. The final Analytics +count remains a separate assertion. + +The recorded-AI fixture creates and deletes its contributor through the owned +work item; unrestricted contributor CRUD stays disabled. External API updates +appear after the user activates **Refresh agent work**, then persist through the +Activity view and reload. These checks establish recorded progress visibility; +they do not establish autonomous agent execution or polling. See the +[2026-10-23 gate review](NEW_USER_GATE_REVIEW_2026-10-23.md) for the failing-before +evidence and scope. + +### Working with my agent + +54. Can I understand from the title page how my agent and I work together? +55. Can I connect an agent from my graph without giving it my login? +56. Can I tell exactly which project and actions its token permits? +57. Can my agent review priorities and leave a question where I can see it? +58. Can I distinguish my note from an authenticated agent's update? +59. Can I revoke access, and does a subsequent agent request actually fail? +60. Does read-only access prevent every supported write operation? +61. Does the UI describe unavailable capabilities honestly (model execution, task creation, MCP/provider connections)? +62. Can I copy the connection details once, hide them, and recover by replacing the credential if I lose it? diff --git a/docs/quality/NEW_USER_GATE_REVIEW_2026-10-23.md b/docs/quality/NEW_USER_GATE_REVIEW_2026-10-23.md new file mode 100644 index 00000000..5efe3cda --- /dev/null +++ b/docs/quality/NEW_USER_GATE_REVIEW_2026-10-23.md @@ -0,0 +1,55 @@ +# Wait for a save acknowledgement before testing saved-state navigation + +Date: 2026-10-23. Source baseline: `f6887577`. Related public tracking: +[issue #156](https://github.com/GraphDone/GraphDone-Core/issues/156). + +## Evidence and cause + +The first combined 41-case gate stopped after 10 passes and three failures. Two +recorded-AI fixtures invoked `createContributors`, which the reviewed security +schema intentionally removed. The 320px editing case clicked Completed and +forced a document navigation without waiting for the mutation. Analytics then +reported one completed item instead of two. + +The contributor failure reproduced on the owned disposable Neo4j stack. Its +correct replacement uses nested contributor creation and deletion on the +authenticated user's own work item. Cleanup verifies that both fixture nodes +were deleted. The Agents page uses `pollInterval: 0` and exposes **Refresh agent +work**; the test now invokes that action before checking an external API update. + +The original 320px test passed on repeat. Its trace recorded the genuine +`UpdateWorkItems` response with HTTP 200 in approximately 54 ms. A controlled +750 ms delay of that exact status request reproduced the original failure: +Analytics stayed at one completed item. The trace had no completed response for +the status request when the test performed a full document navigation +(`responseStatus: -1`). + +## Corrective rule and checks + +Before navigating to inspect saved state, observe the save acknowledgement. +The responsive test now selects the exact edited item and requested status, +requires HTTP success and no GraphQL errors, checks the returned COMPLETED +state and pressed control, and independently reads the status through the API. +It then performs the original navigation and still requires two completed +items in Analytics. The 750 ms delay remains in the 320px case as a regression; +no assertion timeout was increased and no expected value was weakened. + +Focused command, using the Playwright browser revision installed for this lockfile: + +```bash +TEST_URL=http://127.0.0.1:13127 xvfb-run -a npx playwright test \ + --project=GraphDone-Core/dev-neo4j/chromium --headed --workers=1 --retries=0 \ + tests/e2e/graph/recorded-ai-work.spec.ts \ + tests/e2e/graph/responsive-real-workflows.spec.ts +``` + +The nine focused browser cases passed headed, with one worker and zero retries, +on the disposable combined stack. Screenshots were inspected at 320px and +390px. The recorded-AI capture also brings the saved note into the viewport so +its image supplies direct progress evidence. `git diff --check` passed. + +The fixture regression changes test synchronization and authorized fixture +setup. Application code and security controls are unchanged. This does not +establish durability when a user forcibly reloads before acknowledgement, +automatic agent execution, Cloud backend parity, or the live production build. +The full headed `npm run qc:new-user` gate remains required before promotion. diff --git a/docs/quality/OAUTH_IDENTITY.md b/docs/quality/OAUTH_IDENTITY.md new file mode 100644 index 00000000..52586946 --- /dev/null +++ b/docs/quality/OAUTH_IDENTITY.md @@ -0,0 +1,100 @@ +# OAuth identity and account linking + +This is the Core OAuth portion of #184. The separate +[email-link lifecycle](AUTH_LINK_LIFECYCLE.md) defines token purpose and session +revision. These are local implementation contracts, not deployed-provider +acceptance. + +## Provider proof + +| Provider | Stable identity | Proof required for a new account or automatic email link | +| --- | --- | --- | +| Google | Authenticated userinfo `sub`, matching Passport's parsed ID | Exact boolean `email_verified`, plus Gmail address or a nonempty hosted-domain claim | +| GitHub | Authenticated profile ID | Exactly one primary email with boolean `verified: true` from authenticated `/user/emails` metadata | +| LinkedIn | ID-token subject matching authenticated userinfo subject, under issuer `https://www.linkedin.com` | Userinfo email and exact boolean `email_verified: true` | + +[Google's guidance](https://developers.google.com/identity/gsi/web/guides/verify-google-id-token) +distinguishes Gmail/Workspace authority from third-party addresses whose +ownership may have changed since verification. Core applies the conservative +authority requirement to both account creation and automatic linking. It uses +the existing server-side authorization-code strategy and authenticated +userinfo, not a client-submitted ID token. + +[GitHub documents](https://docs.github.com/en/rest/users/emails#list-email-addresses-for-the-authenticated-user) +the authenticated email endpoint and required `user:email` scope. Core fetches +that metadata directly and retains verification/primary flags. Missing, +unverified or ambiguous primary candidates cannot establish a new link. There +is no fallback to the public profile email. The bounded request uses +`per_page=100`; pagination beyond that response is not added. Absent proof fails +closed rather than guessing ownership. + +[LinkedIn documents](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2) +the issuer, userinfo endpoint and optional boolean verification field. The +nine-argument OIDC callback exposes the authenticated ID-token profile. Core +fetches userinfo safely and compares both subjects before using its email claim. +The library's own userinfo loader is explicitly disabled: its malformed-JSON +handler references an undefined callback. This authenticates a provider account, +not a person's legal identity. + +GitHub and LinkedIn profile requests use fixed HTTPS endpoints, reject redirects, +require successful JSON responses, enforce a five-second deadline and cap each +body at 64 KiB. Errors fail authentication without returning provider response +bodies or tokens. Google retains its existing parsed authenticated userinfo path. + +All configured providers use session-bound state. Google/GitHub enable the +existing Passport state store; LinkedIn retains its default session store. +Only configured providers are constructed; a configured ID without a secret +fails startup. The GitHub type package incorrectly narrows the runtime state +option to string; the localized cast preserves boolean `true`. Actual strategy +tests verify state rejection before token exchange and single-use consumption. + +## Linking and recovery + +An existing provider-and-subject binding is authoritative for account selection. +Changing or omitting its email cannot switch the user to another account. +Disabled bound accounts fail before email fallback. New bindings require the +proof above and match only account email, never username. Disabled, unverified or ambiguous +case-folded email matches fail. Verified provider proof cannot attach the mailbox +owner to a preregistered local account whose earlier password/session remains +controlled by someone else. Both helper return and HTTP callback recheck the +active account and the authenticated session revision; they cannot upgrade an +old snapshot after reset or reactivation. + +Existing accounts and provider bindings are retained without a schema migration. +This does not retrospectively prove how an old link was established. An existing +bound subject can continue signing in when its email is absent or unverified. +For a new unproved binding, existing password or email sign-in/recovery methods +remain available; no implicit merge or account transfer occurs. Ordinary local +accounts currently have no implemented verification/linking flow that upgrades +them for automatic OAuth linking. Unverified local accounts therefore cannot add +a new OAuth binding in this change. A future explicit recovery/linking flow must +establish ownership and handle prior credentials/sessions; merely setting a +verification flag is not a recovery procedure. Administrator review of +a disputed historical binding remains a separate action. Old stored provider +credentials are not deleted by this change; new callbacks neither persist +access/refresh tokens or raw profiles nor log token-bearing redirects. + +New users and provider bindings commit together on a dedicated connection to +the persistent SQLite authentication file. A link failure rolls back the user. +The transaction cannot join a caller's unrelated transaction. In-process writes +are serialized per file; bounded asynchronous SQLite busy retries avoid parking +the native worker pool. The database transaction/uniqueness constraint also +protects competing processes. Password hashing happens before acquiring the +write lock. In-memory injected databases are not an OAuth production target. + +## Evidence and limits + +`oauth-profiles.test.ts` covers exact boolean claims, authority, missing and +multiple email candidates, issuer and subject mismatch. `oauth-strategies.test.ts` +drives the actual Passport authorization/callback paths with synthetic token +exchange and profile responses. `oauth-identity.test.ts` uses owned temporary +SQLite files, actual strategy callbacks and a local HTTP completion route. It +covers collisions, subject continuity, disabled/reactivated accounts, concurrent +creation, link rollback, unrelated transaction rollback, revision races, token +issuance and absence of credential logging. + +Synthetic provider fixtures do not test live credentials, provider availability, +consent, TLS, signature-key rotation or hosted D1. Tokens in the browser callback +URL retain the existing application contract; removing server logging and adding +no-store/no-referrer does not claim elimination of all browser-history exposure. +Final exact-head review, CI and production browser results belong in the PR. diff --git a/docs/quality/PASSWORD_LOGIN_LIMITS_2026-10-23.md b/docs/quality/PASSWORD_LOGIN_LIMITS_2026-10-23.md new file mode 100644 index 00000000..eeea9757 --- /dev/null +++ b/docs/quality/PASSWORD_LOGIN_LIMITS_2026-10-23.md @@ -0,0 +1,83 @@ +# Password failures need server-side accounting on each login resolver call + +Date: 2026-10-23. Baseline: `5f0f454`. Tracking: +[Core validation sweep #156](https://github.com/GraphDone/GraphDone-Core/issues/156). + +## Incident and correction + +The password login resolver previously accepted repeated guesses without a +server-side attempt limit. Browser localStorage lockout did not constrain direct +GraphQL clients, and the separate REST email-request limiter did not cover this +resolver. Seven of the first eight new real-SQLite/GraphQL regressions failed on +the baseline; the repeated-success case already passed. + +The resolver now reserves an IP slot before looking up the account and an +account slot before password verification. Reservations count while work is in +flight, preventing concurrent requests from all passing a check before their +failures are recorded. Every invoked login field is charged independently of +its GraphQL operation name or alias. The schema's non-null login field can stop +the remaining aliases after an error; aliases in later requests still share +the same accounting. + +## Selected policy + +| Scope | Policy | +| --- | --- | +| Account | At most five failed or in-flight password-login attempts in a fixed 15-minute window. Known accounts use the SQLite user ID, so email, username, and casing share one budget. Unknown identifiers are normalized. | +| IP | At most 20 failed or in-flight attempts in a fixed 15-minute window across accounts. Express `req.ip` or the connection address supplies the key; this code never reads forwarded headers directly. The installed `ipKeyGenerator` groups IPv6 addresses by /56 and normalizes IPv4-mapped addresses. | +| Success | Refund the reservation and clear that account's earlier failures. Do not clear the IP's failures for other accounts. Repeated successful sequential logins do not exhaust either budget. | +| Failure | Wrong passwords, unknown accounts, disabled-account rejection, and failed optional CAPTCHA verification consume capacity. Account-limit rejection also consumes an IP failure, limiting repeated blocked-account lookups. Internal storage/signing failures release reservations without charging a credential failure. | +| Recovery | Once a budget is exhausted, even a correct password waits for its window to expire. Responses use HTTP 429 and GraphQL `RATE_LIMIT_EXCEEDED`, `retryAfter` seconds, and `rateLimitExceeded: true`. No account is permanently disabled. | +| Storage | At most 10,000 account keys and 10,000 IP keys per process. Keys are hashed to bound their stored size. Successful empty entries are removed; expired idle entries are swept lazily. At capacity, new keys fail closed without evicting an existing blocked key. Pending entries survive expiry until they settle. | + +The fixed window starts when a bucket is first reserved. Successful empty +buckets are discarded. Late failures are charged to the current window, while +in-flight slots remain reserved across a window boundary. Settlement is +idempotent, so cleanup cannot refund the same slot twice. + +## Evidence + +`packages/server/tests/password-login-limits.test.ts` uses the actual resolver, +auth-only GraphQL schema, bcrypt password verification, and real in-memory +SQLite. A separate loopback HTTP listener is allocated on an OS-selected port +for the Apollo HTTP check. No deployed or shared database is used. + +The tests cover direct HTTP 429 responses, aliases, changing account identifiers +and source IPs, concurrent failures, unknown-account spraying, untrusted +forwarded headers, IPv6 rotation, expiry/recovery, internal storage errors, +and 45 consecutive successful logins without browser storage. + +`packages/server/tests/password-login-limiter.test.ts` covers bounded storage, +capacity rejection, expiry, in-flight reservations, double settlement, +concurrent successful/failed attempts, IPv4-mapped IPs, and missing-IP fallback. +The two focused files passed all 16 cases after the change. Commands: + +```bash +npm run test --workspace @graphdone/server -- tests/password-login-limits.test.ts tests/password-login-limiter.test.ts +npm run typecheck --workspace @graphdone/server +``` + +A fresh isolated `npm ci --legacy-peer-deps` on Node 22.23.4 completed. The full +server suite then passed all 44 tests; server typecheck and lint passed with +zero errors, and `git diff --check` passed. Existing lint warnings remain. + +## Limits and remaining work + +Accounting is in process memory. Restarting a process clears it, and multiple +replicas do not share budgets. A distributed deployment needs a shared atomic +store or equivalent gateway controls, plus tests spanning replicas. Shared +NATs or IPv6 prefixes share a failure budget. A caller with no valid IP context, +including the current WebSocket context, shares a bounded unknown-IP bucket. +Correct proxy trust configuration remains necessary; accepting arbitrary +forwarded addresses at the proxy would undermine per-IP attribution. + +An attacker can deliberately exhaust a known account's temporary budget or +tracking capacity. These controls bound guessing and storage; they do not +replace broader abuse protection or prevent denial of service. Parallel valid +logins can be temporarily limited while their reservations are pending. + +This change does not fix the optional/synthetic CAPTCHA path, token purpose, +magic-link races, OAuth account linking, fallback signing secrets, signup +limits, or other authentication entry points. Those require separate reviewed +changes and evidence. The database fixture and loopback API checks do not +establish production deployment parity. diff --git a/docs/quality/PRODUCTION_SECRETS_2026-10-23.md b/docs/quality/PRODUCTION_SECRETS_2026-10-23.md new file mode 100644 index 00000000..8f598716 --- /dev/null +++ b/docs/quality/PRODUCTION_SECRETS_2026-10-23.md @@ -0,0 +1,106 @@ +# Production startup must reject default secrets before authentication initializes + +Date: 2026-10-23. Baseline: runtime branch `5f0f454`. Scope: GraphDone-Core API +startup and production launch configuration; no deployment was performed. + +## Symptom and cause + +The API accepted missing signing configuration in production. JWT signing used +the public fallback in `src/utils/auth.ts`; Express sessions used the public +fallback in `src/index.ts`. The registry Compose file also supplied a published +placeholder. The API image lacked `NODE_ENV=production` when launched directly. + +Additionally, `index.ts` called `dotenv.config()` after importing modules that +capture `JWT_SECRET`. Supplying the intended key only through `.env` could leave +token signing on the fallback captured earlier. Checking environment values +after those imports would not fix that ordering defect. + +Existing guidance was searched in `docs/quality` and `docs/security`; the older +TLS implementation plan listed default JWT secrets but provided no startup +regression. The correction and production setup are now linked from the +[deployment guide](../deployment/README.md#required-production-signing-secrets). + +## Corrective rule + +Load environment configuration as the API entry point's first dependency, then +validate both signing variables before importing authentication or starting a +database or listener. Production rejects absent, empty, whitespace-only and +known published default/example values. Errors contain variable names only. +Development and test retain existing behavior. + +The production API image now declares production mode. All three legacy +production Compose variants explicitly pass both variables without a fallback; +the API rejects empty values. This keeps Compose config validation and +development setup's infrastructure-only start usable. The self-host installer +already generates separate random values and needs no change. The production +CI smoke step generates ephemeral secrets without printing them. + +## Dated regression evidence + +On 2026-10-23, Node 22.23.4 with a clean `npm ci`: + +```text +npm run build --workspace=@graphdone/core +npm test --workspace=@graphdone/server -- tests/production-startup.test.ts + +Same final test against unmodified 5f0f454 index.ts: +6 failed, 2 passed + missing/default production secrets were accepted + .env key verification failed with "invalid signature" + NODE_ENV=production from .env did not enforce secret configuration + +npm test --workspace=@graphdone/server -- tests/production-startup.test.ts tests/production-secrets.test.ts +29 passed (including the additional published Kubernetes placeholder found in review) + +npm test --workspace=@graphdone/server +57 passed across 5 files + +npm run build --workspace=@graphdone/server +exit 0 +``` + +The startup tests import the actual API entry point and use real Apollo/Express +startup and JWT signing. SQLite initialization, Neo4j responses, schema creation +and the optional admin subprocess use test doubles. `Server.listen` is observed +without binding a socket. Positive controls reach that boundary with configured +production values, with `.env` values, and in development without secrets. The +negative controls assert that database initialization and listening never run. +Pure validation tests cover both variables and all known repository placeholders. + +Six additional built-entry subprocess probes ran in temporary working +directories with no shared data and a nonservice Neo4j address. Missing both, +each individually missing, each built-in fallback, and production mode supplied +by `.env` all exited 1 with `Invalid production secrets`, before startup output +or creation of a data directory. Compose JSON validation of each legacy variant +confirmed generated values are forwarded unchanged and absent values remain +empty. Probe output contained only case names and outcomes. + +The final API image also built successfully: + +```text +docker build -t graphdone-audit-secrets:20261023 -f packages/server/Dockerfile . +docker run --rm --network none --read-only graphdone-audit-secrets:20261023 +exit 1: Invalid production secrets: JWT_SECRET, SESSION_SECRET +``` + +Six network-disabled, read-only container probes (missing values individually +and together, both built-in fallbacks, and the Kubernetes placeholder) rejected +configuration before startup without an explicit `NODE_ENV` override. A seventh +container loaded the actual environment bootstrap with generated secrets and +verified a JWT against the configured key. That positive container probe did +not start the full API; the actual-entry tests above establish its listen +boundary without shared sockets or databases. + +## Compatibility and limits + +Existing production installations using missing/default values must configure +both variables before upgrading. Keep them stable across restarts; changing +JWT/session signing keys invalidates existing tokens/signed sessions. Generate +independent cryptographically random secrets; rejection of known placeholders +is not an entropy test for arbitrary values. + +These checks establish the startup configuration boundary, not a complete +production deployment or database/browser health check. Existing Express +MemoryStore warnings remain visible in the positive tests; session storage, +OAuth, CAPTCHA and key rotation mechanisms are outside this change. No hosted +configuration or secret was read or changed. diff --git a/docs/quality/RELEASE_GATES.md b/docs/quality/RELEASE_GATES.md new file mode 100644 index 00000000..7736a662 --- /dev/null +++ b/docs/quality/RELEASE_GATES.md @@ -0,0 +1,64 @@ +# Main promotion and release gates + +Source promotion, image publication and deployment are separate operations. +The production gate runs on main-target PRs, main pushes, nightly schedules and +manual CI dispatch. `CI Success` waits for it and rejects failed, cancelled, +missing or skipped results when selected. Dev-only events may legitimately skip +that job; their other required jobs must still pass. + +## Publishing a release + +Docker Build Validation builds the web, API and Neo4j images for amd64 and arm64 +on main-target PRs without registry writes. + +A release is published only by pushing a version tag on a validated main commit: + +1. Bump the version in a PR to dev (`scripts/update-version-minimal.sh ` + plus the workspace manifests and lockfile), then promote dev to main. +2. Wait for main's CI and Comprehensive Tests on the merge commit to pass. +3. `git tag -a v
-m "GraphDone v" && git push origin v` + +Only admins can create `v*` tags (a repository tag ruleset). +[Release](../../.github/workflows/release.yml) then runs +[`scripts/verify-release.mjs`](../../scripts/verify-release.mjs), which fails +closed unless: + +- the tag is `vMAJOR.MINOR.PATCH[-suffix]` and points at a commit on main's + first-parent line — a commit main itself was at, not a dev commit merely + merged into it (dev pushes skip the production gate); +- every workspace manifest carries that version; +- in the CI and Comprehensive runs that a push to main triggered for that exact + commit, the newest attempt of each required job (`CI Success`, both Node unit + matrices, `Installer & Deploy Config`) completed successfully. Pull-request + runs test a merge ref, so they never count. + +Only then do write-scoped jobs build `ghcr.io/graphdone/graphdone-{web,api,neo4j}` +from the verified SHA for amd64 and arm64 (SBOM and provenance, the same web +build arguments as the validated stack) and push the version tag. After every +image is published, the final job decides from the published releases whether +this version is the highest — a pre-release never outranks a stable release — +moves `:latest` on all three images together if so, and creates the GitHub +release with the image digests. Each tag runs in its own concurrency group and a +started release is never cancelled. The installer and +`deployment/docker-compose.registry.yml` pull `:latest`. + +This repository does not deploy. graphdone.com is built from the `core/` +submodule of GraphDone-Cloud and deploys from that repository's main branch; +bump the submodule to the release tag there to ship it. + +## Production acceptance + +`xvfb-run --auto-servernum node scripts/test-production-stack.mjs --out test-artifacts/prod-stack` + +The owned fixture runner builds the real Compose stack with private generated +credentials, waits for HTTP/application/database readiness, seeds bounded graph +fixtures and runs the strict user smoke sequence. It also checks authentication +persistence across API-container recreation with the same owned volume. Evidence +includes the source revision, stage outcomes and cleanup. Never point this runner +at existing user containers, databases or volumes. + +Workflow regressions are part of `npm run test:unified:lib`. Run the production +command on a clean, exact candidate and retain its evidence before promotion; +review actual final main CI before closing the promotion issue. See +[the test strategy](TEST_STRATEGY.md) and +[issue #189](https://github.com/GraphDone/GraphDone-Core/issues/189). diff --git a/docs/quality/RELEASE_QC.md b/docs/quality/RELEASE_QC.md new file mode 100644 index 00000000..98886e33 --- /dev/null +++ b/docs/quality/RELEASE_QC.md @@ -0,0 +1,176 @@ +# Release QC ledger + +Target: free Core UI, Cloudflare Pages static frontend plus supporting API/D1. Advanced managed autonomous-agent execution is deferred by product direction. + +| Question group | State | Evidence / next check | +| --- | --- | --- | +| Guest dialog and narrow-screen entry (2–4, 39–41) | PASS, scoped | Five headed guest-layout cases; verification gate retained; no unnecessary overflow at 320px portrait. Hosted guest copy now comes from API configuration and explains 24-hour access and deletion. | +| Direct editing and open-dialog rotation (15–17, 38–41) | PASS, scoped | Seven headed isolated-data responsive journeys; exactly one editor opening action. Native virtual keyboard remains unverified. | +| Overview/Board opening (25–28, 36) | PASS, scoped | Headed portrait/landscape direct opening and Board keyboard journey. | +| Real recorded progress (18, 31–32, 35) | PASS, scoped | Recorded-activity unit tests, Core API-to-UI journeys, local D1 progress persistence journey. Autonomous execution and append-only attributed history are unavailable. | +| All eight workspace modes + Analytics with the same saved data (24–32, 46–48) | PASS, scoped | Three headed viewport journeys on each backend (360×640, 844×390, 1440×900); UI navigation, saved date/tag readback, totals and reload. Does not prove every control in every mode. | +| Grow, relationships, undo and failure recovery (19–22) | BLOCKED | Existing Core smoke and interaction tests need inclusion in the repeatable gate; hosted parity still needs coverage. | +| Accounts, permissions and private isolation (5–6, 34, 47, 49) | BLOCKED | Local Cloud multi-graph/cross-account CRUD passes; email/provider and full role/membership flows are not verified. | +| Accessibility beyond emulation (37–43) | BLOCKED | Keyboard dialog checks exist; native mobile keyboard, screen reader, zoom and full contrast audit incomplete. | +| Pages artifact, routing, API, cache and release operations (44–45, 50–51) | BLOCKED | Build/deploy separation, exact source integration and staged production artifact check in progress. | + +A BLOCKED row prevents declaring the full app production-ready. Release a deliberately bounded preview only with a clear capability statement and retained evidence. + +## New-user sweep, 2026-09-07 + +Evidence is local development unless explicitly stated. Core authentication uses the seeded admin; local D1 uses isolated ephemeral guests. Neither proves production account onboarding or multi-user teams. The six persona lifecycle cases passed on both backends; they exercise real UI creation, progress notes, status changes, return and totals. Shared-data modes passed on both backends. Core's 19 existing responsive/progress/admin checks and 235 web unit tests passed; follow-up changes are covered by the final gate log. The full historical MCP chaos suite is not an all-green baseline (earlier memory exhaustion). + +| Questions | Result / observation | +| --- | --- | +| 1, 4, 23, 36 | BLOCKED (partial evidence): creation is now reachable from every view, defaults need only a title; no measured action-count budget across all journeys yet. | +| 2–3 | PASS scoped: guest modal/verification/layout; hosted copy describes editable temporary work. Remote expiration/provider journey unverified. | +| 5–6 | BLOCKED: dev sign-in works; account/email recovery and all unavailable-provider states need target-environment evidence. | +| 7–9, 12 | PASS scoped: selected graph data and every view reachable on portrait/landscape/desktop. | +| 10 | PASS scoped: editor/admin/guest Escape and focus checks; not every dialog audited. | +| 11 | BLOCKED (partial evidence): local route reload works; staged Pages deep links checked separately below; Back/Forward full journey pending. | +| 13–18 | PASS scoped: six UI-created lifecycle fixtures persist title, notes and status; UI deadline entry covered; D1 date/tag create/edit/clear contract passes. | +| 19–21 | BLOCKED (partial evidence): Core grow/undo smoke and D1 connection integrity checks; complete hosted UI connection/undo journey pending. | +| 22 | BLOCKED: interrupted-save/retry and conflict behavior not verified end to end. | +| 24–32 | PASS scoped: every workspace view renders the same fixture and real Analytics totals; Timeline no longer invents deadlines; Calendar retains full task text. This is not all controls/filters/date navigation. | +| 33 | BLOCKED (partial evidence): usage counts are real and layout audited; full schema/type management contract not implemented on D1. | +| 34 | BLOCKED: dialogs fit; persistence of every settings/admin action and role matrix not verified. | +| 35, 52 | PASS scoped: recorded AI contributions distinguished from execution; hosted MCP now reports unavailable rather than falsely connected. Managed autonomous agents remain deferred. | +| 37–38 | BLOCKED (partial evidence): keyboard/dialog checks and responsive journeys pass; all targets and screen-reader use not audited. | +| 39–40 | PASS scoped: guest overflow regression and mode page-width checks pass; real oversized content retains scrolling. Some complex views legitimately require scroll. | +| 41 | BLOCKED (partial evidence): open editor rotation tested; native virtual keyboard/browser zoom pending. | +| 42–43 | BLOCKED: full empty/loading/error/offline/high-contrast/reduced-motion matrix is incomplete. | +| 44–45 | BLOCKED pending staged-artifact verification; live deployment not performed. | +| 46, 48 | BLOCKED (partial evidence): D1 task lifecycle, all views, dates/tags/notes and connection API contracts pass; memberships, assignments and settings parity incomplete. | +| 47 | PASS scoped: local D1 cross-account CRUD isolation and mixed-graph rejection; full team role matrix remains blocked. | +| 49 | BLOCKED: credential expiry and reconnect recovery need a complete journey. | +| 50–51 | BLOCKED (partial evidence): separate build/publish, restricted function routes, headers and release manifest added; target secrets, remote migrations and rollback exercise not verified. | + +The standard catalog and result format are `SCENARIOS.md` and `SCENARIO_RESULT_TEMPLATE.md`. Its multi-account, scale and interruption scenarios remain explicit release blockers. Single-actor fixtures set in larger teams must not be described as load tests or team collaboration coverage. + +### Evidence files for this sweep + +- `/tmp/graphdone-core-quality-final.log`: 33 passed before the subsequent graph-motion, details and resize changes; this is not a final rerun of the expanded gate. +- `/tmp/graphdone-cloud-quality-final.log`: 8 passed, 1 startup/loading timeout; `/tmp/graphdone-cloud-large-final.log`: isolated retry passed. Keep both results; do not erase the initial failure. +- `/tmp/graphdone-core-modes.log`: 3 passed; `/tmp/graphdone-core-personas.log`: 6 passed. +- `/tmp/graphdone-qc-unit-final.log`: 235 web tests passed. +- `/tmp/graphdone-qc-build-final.log`: web build passed; bundle-size warning remains. +- `/tmp/graphdone-qc-lint-final.log`: zero errors, 702 warnings (not a warning-free baseline). +- `test-artifacts/quality/`: persisted screenshots labelled by backend, viewport/mode or scenario ID. Screenshots are evidence, not substitutes for assertions. + +Cloud API local contract scripts cover separate owned graphs, mixed-graph rejection, foreign reads/writes, edge cascade integrity, and date/tag create/edit/clear. No remote migrations, emails or production writes were performed during this sweep. + +### Graph motion investigation + +The strengthened desktop drag test failed before the change because the simulation kept running after a verified saved drag. The first fix passed phone portrait and landscape but found a 545px desktop jump while idle after a type edit. Rebuilt SVG node groups had no position until the next physics tick. Nodes now receive their saved translation immediately; retired simulations stop and lose callbacks; pinned graphs do not reheat on release/resize; configured damping is preserved. Raw motion samples are written to `test-artifacts/quality/motion-.json`. Final rerun results are recorded below. + +Hands-on local browser pass: created the private `Graph-first hands-on QC` graph through the graph selector, created a task through the form, dragged it with pointer down/move/up, then used the node's + control and canvas click to grow and name a second connected task. Screenshots: `hands-on-created.png`, `hands-on-dragged.png`, `hands-on-grow-mode.png`, `hands-on-connected.png`. This graph is retained for continued UI inspection; no unrelated graph was edited. + +Graph-details follow-up: `/tmp/graphdone-core-details.log`: 3 headed tests passed at phone portrait, landscape and desktop. These verify status display, retaining unsaved date/tag edits across polling, saving them, clearing the date, and remaining in Graph view. + +Resize regression: `/tmp/graphdone-resize-before.log` reproduced a 60px centering error during incremental desktop growth. The camera observer now handles every actual nonzero size change, coalesced per animation frame, with no overlapping zoom transitions. It does not modify saved node coordinates. `/tmp/graphdone-resize-motion-after.log`: all 4 headed tests passed, including the incremental grow/shrink/rotation regression and three drag/edit stability cases. The resize case also verified identical persisted node coordinates. `/tmp/graphdone-web-graph-unit.log`: 235 web tests passed; `/tmp/graphdone-web-resize-build.log`: build passed with the existing chunk-size warning. + +### Expanded review (2026-09-07) + +- `/tmp/graphdone-qc-expanded.log`: 40 headed Core checks passed before the subsequent stacked-layout, Turnstile and visual changes. +- Local D1 security checks reproduced a named-fragment depth-limit bypass. The validator now expands fragments, rejects cycles and caps expanded selections; `/tmp/graphdone-security-after.log` passes 7 checks, including eight concurrent batches at the 300-node boundary (one accepted, seven rejected). This local result does not prove distributed quota atomicity. +- `test-turnstile.mjs` passes rejection/action/hostname/outage verification cases. Production keys are not configured. +- Dependency audit (`npm audit --omit=dev`, `/tmp/graphdone-dependency-audit.json`) reports 57 advisories: 4 critical, 25 high, 25 moderate, 3 low. These are dependency findings, not evidence of exploitation; remediation remains required. +- `/tmp/graphdone-layout-before.log`: stacked saved cards failed the new geometric gate before repair. `/tmp/graphdone-layout-controls-after.log`: 5 passed: all quick-editor types/statuses on phone and desktop, and card separation at 12/50/200 nodes across reload. This is not proof of label/edge clearance or a maximum supported graph size. +- `/tmp/graphdone-studio-unit.log`: 237 web tests passed. `/tmp/graphdone-studio-build.log`: build passed with the existing bundle-size warning. + +Final checks for this iteration: + +- `/tmp/graphdone-button-content-final.log`: 728 visible button instances inspected across seven routes and seven viewport sizes, including project menus; zero content-overflow failures. The project name now has at least 100px usable space. This is not coverage of every hidden submenu or action. +- `/tmp/graphdone-button-turnstile.log`: both official-test-key Turnstile guest journeys passed (390×844 and 844×390); its separate button scan failed on nested main landmarks, which was fixed and rerun above. No real production widget was configured. +- `/tmp/graphdone-visual-verification.log`: nine guest-layout, drag-stability and resize checks passed; initial two Turnstile assertions failed and were corrected in the later run (config test fixture and phone selector). +- `/tmp/graphdone-ui-final-build.log`: build passed; `/tmp/graphdone-ui-lint.log`: zero errors, 713 warnings. +- Visual direction: charcoal surfaces, static fine texture on sign-in, solid wordmark, editorial heading, quieter card headers, small completion marks and full-label zoom fading. Screenshots in `test-artifacts/quality/studio-*.png`. This is a first visual pass, not a complete redesign of every screen. + +`/tmp/graphdone-iteration-final-unit.log`: 237 web tests passed after the visual and selector changes. Final whitespace validation passes. The first sign-in screenshots still show the legacy math challenge on Core email login; this iteration integrates Turnstile for hosted guest entry only. Account/email verification migration remains open. + +## Human–agent connection iteration, 2026-09-07 + +- Core server typecheck and web production build pass. Web unit suite: 237 passed. Existing bundle-size warning remains. +- `graphdone-agent-review.log`: 7 passed (three human–agent journeys, three modal/header/Escape journeys, 742 button instances with zero content failures); three visual cases failed because their expected headline was stale. +- `graphdone-agent-final-headed.log`: all 6 passed after updating the headline assertion and making the agent panel header sticky (three agent journeys and three visual cases). This also covers the Core active-token limit implementation on ordinary issuance, not a 25-token concurrency stress test. +- `graphdone-cloud-agent-headed.log`: all 3 hosted UI cases passed against local D1, plus the D1 token/notes/progress/security contract. Local migration 0007 applied. This is not a production deployment. +- New repeatable commands: `qc:agents`, `qc:modals`; new OpenAPI document and connection guide in `docs/agents/`. Cloud headed contract invocation: `DISPLAY=:1 PLAYWRIGHT_BROWSERS_PATH=/tmp/graphdone-playwright QC_AGENT_HEADED=1 node scripts/test-agent-connections.mjs`. +- Remaining: provider/MCP adapters, task creation/deletion through agent API, shared-team issuance permissions, distributed quota/retention stress, and previously recorded dependency/security/release blockers. Core note/token cleanup following deleted projects still needs lifecycle integration; D1 uses cascading foreign keys. + +### Main-site recovery requested + +Public checks at 2026-09-08 04:11–04:13 UTC: `graphdone.com` and `www.graphdone.com` return Cloudflare 522; `graphdone-cloud.pages.dev` returns 200; production API health returns healthy. Main-domain API preflight lacks Access-Control-Allow-Origin, so custom-domain recovery must include API origin configuration. Cloudflare CLI authentication is expired (OAuth refresh 400). Reauthentication opened in the headed browser; no remote mutation performed while awaiting access. + +### 2026-09-08 live signup incident and expanded agent gate + +The signup hotfix is live separately from the graph UI work. See GraphDone-Cloud `docs/PAGES_RELEASE.md` for release details. Production was tested using the owner's delivered code through the actual form, then UI project/task creation and reload at 390×844, 844×390 and 1440×900; zero browser console errors. An isolated local D1 lifecycle test caught and fixed email-prefix username collisions in new-account creation. Existing-account login is not counted as new-account inbox verification. + +Real-inference gate: `QC_REAL_AGENTS=1 TEST_URL=http://localhost:3127 npm run qc:real-agents` (added alongside this entry) uses disposable local Core projects, reads the actual copied UI prompt, and runs installed Claude/Codex CLIs in scratch directories. Default models: Claude Haiku and GPT-5.4-mini, overridable with `QC_CLAUDE_MODEL` / `QC_CODEX_MODEL`. Each run has a 180-second process cap; Claude additionally has a $0.25 CLI cap. Codex has no dollar-budget cap in this harness: do not expand it into thousands of runs without gateway-enforced spending limits. Credentials are revoked in cleanup; redacted transcripts go to ignored `test-artifacts/quality/real-agent-*.json`. Only the one student handoff is covered so far; model-generated work quality still needs human review and additional persona/adversarial scenarios. + +The first Claude Haiku handoff completed and was visible in the UI. An early revocation check raced the UI request; the regression now waits for completed revocation. Codex GPT-5.4-mini also passed the saved-state/UI/revocation assertions through the test-only local MCP adapter. Its shell attempt failed because this host cannot create the bwrap UID map. The adapter exposes only context/update/note operations on localhost:3127, caps calls at 20, and preauthorizes only these three explicitly requested fixture operations for the CLI invocation; user/global Codex configuration is not modified. Gateway credentials have not yet been located. No remote D1 is used by these inference cases. + +Hosted fair-share admission has a separate prototype in `/tmp/graphdone-usage` (`improve/fair-usage`). Its 10,000 allocation-policy attempts are not end-to-end UI tests or model runs, and production enforcement/UI remain pending. Do not claim complete coverage or per-user daily protection from this policy result. + +Reported-controls follow-up: all 3 headed cases passed at 844×390, 1440×900 and 2560×1440 after the empty-alert helper fix. They exercise account-menu visibility/hit-testing/Escape focus, disabled template messaging, hierarchical card separation, and three settled minimap reopen cycles. Compact-layout unit suite: 18 passed, including mixed card sizes and 200 independent tasks without card overlap. Web typecheck passed. This does not establish edge-label clearance or readability of every title in a 200-node overview. + +## Hosted allowance follow-through — 2026-09-08 + +Cloud `docs/FAIR_USAGE.md` is the enforcement contract. Core's hosted build shows +actual account operations/row counters and local reset times, with 80/95/100% +warning states. Self-hosted Core has no hosted limits or usage UI by default. + +Repeatable headed scenario: `tests/e2e/quality/hosted-usage.spec.ts`, with +`VITE_HOSTED_USAGE=true` on local Vite. Six viewports (320 portrait through 2560 +landscape), personal/shared pauses, simulated reset, rejected task creation, +retained text, blocked reload, retained login, hit-tested recovery download. +All six passed. The test uses local Core and intercepted quota responses; +Cloud's complementary `test-hosted-usage.mjs` exercises actual local D1/DO, +including signup, agent owner accounting, concurrency, failure and UTC reset. + +The harsher blocked-reload test exposed a real empty-workspace overflow that +covered the recovery button. The empty panel now scrolls within its own bounds. +Quota errors no longer clear cached authentication or auto-create welcome graphs. +Reported controls regression (three headed sizes) passed: user-menu placement, +nonfunctional template labeling, minimap reopen and compact hierarchy. + +Recovery records are per-tab, per-account sessionStorage (last 20 distinct +unconfirmed graph/task operations). They can be downloaded after reload but are +not an offline database or automatic retry queue. Check for partial server +success before reapplying. Full offline guests/sync, comprehensive security +clearance and all-persona inference sweeps remain separate work. + +## Offline storage and sync — September 8, 2026 + +The managed client now has an account-scoped IndexedDB project store, local-first task/position/edge/project mutations, human note persistence, an exact-request upload queue, three-way conflict review, backup export/import, local-only guest onboarding, and a precached production app shell. Enable with `VITE_OFFLINE_SYNC=true` only against the compatible managed `/api/sync` backend. Standalone Neo4j remains unchanged by default. + +Repeatable sequence: start Cloud `scripts/test-offline-sync.mjs --serve`, build Core with the local API at `http://localhost:8798` and both `VITE_OFFLINE_SYNC=true` / `VITE_HOSTED_USAGE=true`, preview on 3136, then run `DISPLAY=:1 PLAYWRIGHT_BROWSERS_PATH=/tmp/graphdone-playwright TEST_URL=http://localhost:3136 npm run qc:offline`. Restart the isolated fixture server between full sweeps to reset its quota ledger. Run headed tests serially without source/build changes during a run. + +Latest result: **3 headed scenarios passed (50.9 seconds)**. Student team: six viewport bounds, real offline task creation and mouse drag, table/card/Kanban/Gantt/calendar/activity/graph navigation offline, offline reload, persisted quota deadline with no retry loop, reconnect and exact-once remote creation, second tab, real local agent token, offline human note, competing agent priority update, explicit conflict resolution, credential-free export and imported project with new IDs. Guest: no cloud mutation during onboarding and offline reload. Parent: school pickup task survives closing the entire browser and reopening its profile offline. + +Nine unit cases passed, including 1000 deterministic disjoint human/agent edit scenarios, delete/edit conflicts, edits made during an in-flight upload, bounded dependency-ordered batches, and malformed backup rejection. Cloud's real local D1 tests cover replay, owner isolation, atomic revision races, agent writes, 2000 full-length notes (8 MB), metering regressions, and signup regressions. This does not claim exhaustive browser/device/security coverage or thousands of inference calls. + +Downloaded projects work offline; unopened projects need an initial download (Settings can prepare all). Uploads resume while the app is open and cloud access is available. Browser data clearing can remove local copies; export important work. Account records are currently rewritten as one transaction, so very large accounts still need low-memory-device benchmarking. Agent credentials and account administration remain online-only. The hosted backend's existing sharing and unsupported-property limitations remain. + +Live acceptance found the legacy red connection overlay could cover the inspector after an offline reload. Offline-enabled workspaces now use the in-flow storage status instead; connection and usage indicators refresh on reconnect. The local gate now deliberately aborts health probes and still clicks Edit. Agent-token controls are disabled offline while notes remain usable. Local resource caps reject an over-limit edit before acknowledgement, and the initial hosted account project cannot be deleted locally. A tenth unit case covers deletion while a create acknowledgement is in flight, ignoring server timestamps when detecting real conflicts. Latest expanded headed result: **3 passed in 52.4 seconds**. + +Quota recovery additionally retains the account profile in IndexedDB. A returning token can recover the cached profile without a D1 `Me` query, while uploads still require server authorization. Profile caches are excluded from project exports. The final local sweep explicitly removes the browser's currentUser cache, reloads during a persisted quota pause, asserts zero network `Me` requests, and recovers both tasks: **3 headed scenarios passed in 48.9 seconds**. + +A persisted upload attempt counter now prevents silent resurrection after receipt retention: a retried creation missing from the cloud requires review after a revision conflict. This covers a lost acknowledgement followed by another device deleting that item, even after the server's bounded receipt history expires. Eleven unit cases passed; the full headed sequence passed again in **48.6 seconds**. + +## 2026-09-08 — Individual project transfer and Mermaid + +New repeatable gate: `npm run qc:project-transfer` (headed, serial; local offline-enabled production build). Full format contract and JSON example: `docs/formats/PROJECT_TRANSFER.md`; public JSON Schema: `packages/web/public/schemas/project-v1.schema.json`. + +| ID | New-user question / persona | Sequence | Required result | +| --- | --- | --- | --- | +| TRANSFER-01 | Student: Can I share just this project? | Import documented example via file chooser, select it, export JSON | Exactly one project; task fields, positions, tags, metadata, links and notes preserved; no auth or sync internals | +| TRANSFER-02 | Parent / team: Will importing overwrite my work? | Reimport export offline, reload | Fresh project/task/link/note IDs; references remapped; original unchanged; durable local copy | +| TRANSFER-03 | Engineer: Can I put this in documentation? | Export Mermaid with adversarial quotes, emoji, HTML and directive-like task text; parse/render locally with Mermaid 11.17.2 | Three readable nodes, directed labeled edge, no executable user syntax; actual SVG screenshot | +| TRANSFER-04 | Any user: What if a file is broken or my disk is full? | Invalid JSON/version/references, mixed valid-invalid backup, >50 MB file, injected IndexedDB put failure | Visible error, zero partial imports; later saves still work | +| TRANSFER-05 | Large team: What are the limits? | 300 tasks / 600 links / 2,000 full-length notes round trip; over-limit project; account with 100 cloud summaries | Full-size data round trips; excess rejected without saving | +| TRANSFER-06 | Mobile user: Can I reach every action? | Click JSON export, Mermaid export and import file chooser at 320×568, 390×844, 844×390, 1024×768, 1440×900, 2560×1440; actual import at 320 portrait | Downloads work; buttons fit; menu is bounded and scrolls only its contents; no page overflow | +| TRANSFER-07 | Returning user: Can I preserve notes for deleted tasks? | Export historical note whose task no longer exists | Note body and author label preserved as a project note | + +Evidence: 26 unit/schema/reconciliation tests passed (including 1,000 deterministic merge cases). Local transfer headed gate passed in 12.7 s; local offline headed regression 3/3 passed in 49.8 s. Actual local Worker/D1/DO sync fixture passed owner isolation, idempotent replay, atomic races, real REST agent revision conflict and 8 MB notes download. Production build/typecheck passed. Mermaid/Ajv are dev-only validation dependencies and do not ship in the app bundle. Screenshots under ignored `test-artifacts/quality/project-transfer-*.png` and `project-mermaid.png`. + +Scope: hosted/offline-enabled app. Mermaid is export-only and omits notes, descriptions, dates, priorities and exact layout; use JSON for restoration. This is targeted transfer/offline validation, not a claim of exhaustive browser or security coverage. diff --git a/docs/quality/REMAINING_TOOLING_2026-10-27.md b/docs/quality/REMAINING_TOOLING_2026-10-27.md new file mode 100644 index 00000000..9d80945d --- /dev/null +++ b/docs/quality/REMAINING_TOOLING_2026-10-27.md @@ -0,0 +1,77 @@ +# Turbo and Mermaid consumer migration — 2026-10-27 + +This completes the remaining npm-tooling slice of #185. It is stacked on the +reviewed runtime-packaging and camera corrections. It does not close Debian OS +triage #209, authorize publication or establish deployed-site clearance. + +## Changes and compatibility + +Turbo 1.13.4 becomes 2.11.10. The configuration uses `tasks` and declares the +existing npm 10.8.2 package manager. Explicit `envMode: loose` preserves the old +task environment behavior; this change does not silently adopt Turbo 2's strict +environment filtering. Build-affecting declared variables, including the offline +flag, remain in the cache hash. Task dependencies, outputs and persistent dev +tasks retain their previous meaning. No remote-cache login or publication is +performed by the contract tests. + +The installed Turbo contract executes an owned two-workspace build: dependency +output must exist before its consumer, an unlisted environment variable must +arrive, changing the offline flag must invalidate both outputs, and an unchanged +run must reuse the cache. A real upstream task exits 23 with a unique marker; +the runner must fail and must not execute its dependent. Fixture removal is strict. + +Mermaid 11.17.2 becomes 12.2.0. Its Node consumer resolves KaTeX 0.18.10; the +shipped browser IIFE embeds KaTeX 0.18.11. Both are above the advisory's 0.18.2 +patch boundary. Mermaid remains a development/test dependency: the app exports +diagram text and does not load Mermaid into its application bundle. Browser +contracts load the actual installed IIFE from the web workspace, so a changed +npm hoisting location cannot silently select a different consumer. + +Mermaid 12 changes default layout/appearance and raises its own browser floor. +The export acceptance explicitly selects dagre/default/classic to preserve its +existing rendering expectation. Node 22.18 and the installed Chrome meet the new +tool's requirements. GraphDone's application build target is unchanged; these +tests do not claim the Mermaid 12 renderer runs on older supported app browsers. + +Only these dependency families and their required transitive package placements +change. No production dependency version changes, blanket overrides or runtime +image package additions are included. + +## Evidence and limits + +- Clean Node 22.18/npm 10.8.2 install succeeds. Fresh full and production npm + audits both report zero findings on 2026-10-27; retained JSON records the dated + advisory result, not a permanent absence of vulnerabilities. +- All-package build, types and lint pass. Coverage executes 715 passing tests + plus the existing 26 explicitly skipped database cases. Those skips are not + database acceptance; the separate real-Neo4j CI contract remains required. + Harness 192/192 passes. Existing warnings remain. +- Installed Turbo, Mermaid/KaTeX and Vite contracts pass 3/3 on Node 22.18 and + Node 24.22.1. Vite checks include real HMR state preservation, HTTP/WebSocket + proxying, secret-file denial and offline-shell behavior. +- The old installed KaTeX 0.16.47 fails the inherited-trust regression: an + options prototype can enable a JavaScript link. The patched consumer rejects + it. The expression is inert test text; no link is clicked or script executed. +- An initial Turbo probe used mutually exclusive `--cache` and `--force` flags + and failed before running a task. That failure is retained, not credited as + task-failure propagation. The final probe uses local write-only cache mode and + requires the actual failing child's unique output marker. +- The final composed offline production build and full portable-project browser + journey pass (27.7 seconds, one worker, zero retries). The journey checks actual + import/export, offline reload, malformed/quota failures, real exported Mermaid + rendering, responsive controls and zero sync writes. Independent implementation + review also repeated the two new contracts and old/new KaTeX negative. Final + documentation review and exact-head CI are pending. + +Local evidence is under the owned `/tmp/graphdone-resolution-20261027` directory +in `remaining-tooling-*` and `katex-inherited-trust-negative.log`. Final CI must +repeat applicable image, browser and Node-version gates; previous stack heads +do not establish acceptance of a changed composition. + +Primary references: + +- [Turbo migration and environment behavior](https://turborepo.dev/docs/crafting-your-repository/upgrading) +- [Turbo package-manager detection advisory](https://github.com/vercel/turborepo/security/advisories/GHSA-3qcw-2rhx-2726) +- [Turbo login callback advisory](https://github.com/vercel/turborepo/security/advisories/GHSA-hcf7-66rw-9f5r) +- [Mermaid 12 breaking changes](https://github.com/mermaid-js/mermaid/releases/tag/mermaid@12.0.0) +- [KaTeX inherited-trust advisory](https://github.com/KaTeX/KaTeX/security/advisories/GHSA-238p-pmpm-9mq7) diff --git a/docs/quality/RUNBOOK.md b/docs/quality/RUNBOOK.md new file mode 100644 index 00000000..162cb083 --- /dev/null +++ b/docs/quality/RUNBOOK.md @@ -0,0 +1,40 @@ +# Repeatable quality sequences + +Run from GraphDone-Core. These commands use disposable local fixtures. Never substitute a production URL for a local stress or security target. + +## Prerequisites + +Core frontend: `http://localhost:3127`; Core API: `http://localhost:4127` with local Neo4j and seeded test admin. For Cloud contracts, run the sibling Cloud Worker with its local migrated D1 database on port 8797; the Cloud-configured frontend runs on 3129. + +On this workstation: + +```bash +export DISPLAY=:1 +export PLAYWRIGHT_BROWSERS_PATH=/tmp/graphdone-playwright +export TEST_URL=http://localhost:3127 +``` + +Elsewhere, install Playwright Chromium and use your normal headed display. Run one headed browser suite at a time so background throttling does not invalidate motion/performance measurements. Finish source edits before starting: development hot reload can reset a dialog mid-test. + +## Standard order + +1. `npm run build --workspace=@graphdone/web` — TypeScript and production build must pass; record bundle warnings. +2. `npm run test --workspace=@graphdone/web` — web unit suite must pass. +3. `npm run qc:new-user` — responsive modes, guest/admin dialogs, recorded AI work, task lifecycles, graph editing, motion, resize and button fit. No skipped or timed-out case counts as a pass. +4. `npm run qc:graph-first` — create a graph and task entirely through the UI, verify a real saved drag, grow and name connected work, post progress, and reload. API calls verify and clean up; they do not create the fixture. Then `npm run qc:controls` — every quick-editor type/status and keyboard priority, with backend readback, at phone and desktop sizes. +5. `npm run qc:layout` — 12 stacked, 50 and 200 connected cards; zero card overlaps on load and reload. This measures tested sizes, not an upper limit or label/edge-routing clearance. +6. `npm run qc:verification` — hosted-guest widget UI with Cloudflare's official public testing key, token transport and phone portrait/landscape fit. This uses the Core test backend; the Cloud verifier is tested separately below. It does not validate production Turnstile configuration. +7. `npm run qc:agents` — human creates project-scoped access, agent reads and updates work, both leave notes, read-only and revocation boundaries are enforced. Then `npm run qc:modals` for header clearance and Escape dismissal. `npm run qc:visual` — captured desktop, portrait and landscape sign-in/workspace states. Inspect the screenshots; automated geometry alone cannot judge visual quality. +8. In GraphDone-Cloud run `node scripts/test-security-limits.mjs`, `node scripts/test-multiple-graphs.mjs`, `node scripts/test-date-tags.mjs`, and `node scripts/test-turnstile.mjs`, and `node scripts/test-agent-connections.mjs`. Each must exit zero. Local quota concurrency does not prove a distributed production limit; mocked Siteverify cases do not prove production keys. +9. Run `QC_BACKEND=cloud QC_API_URL=http://localhost:8797/api/graphql TEST_URL=http://localhost:3129 npm run qc:modes`, then `qc:personas` with the same variables. These test hosted-schema behavior against local D1. +10. `npm audit --omit=dev --json` and web lint. Preserve findings and triage them; do not turn an audit failure into a passing security claim. + +Before release, follow GraphDone-Cloud `docs/PAGES_RELEASE.md` to build and validate the exact Pages artifact, then finish `RELEASE_QC.md`. Publishing is a separate step. + +## Hands-on sequence still required + +Use `GRAPH-FIRST-01` in `SCENARIOS.md`: create a private graph using the selector, create a task through the form, pointer-drag it, grow/name connected work, edit details, change/reverse/remove a connection, undo, and reload. Record screenshots and saved results in `SCENARIO_RESULT_TEMPLATE.md`. The prior hands-on pass covered create/drag/grow/details; its create/drag/grow/progress/reload sequence is now codified in `qc:graph-first`; complete connection-menu and undo evidence is still pending. Do not replace these missing assertions with a claim that every UI control is covered. + +## Evidence and limits + +Screenshots live under `test-artifacts/quality/`. Playwright traces, failures and attachments live under `test-artifacts/playwright-output/`; test logs should be retained with the source revision. `RELEASE_QC.md` records the actual runs from this session, including initial failures and retries. `TEST_STRATEGY.md` lists uncovered security, collaboration, accessibility and stress scenarios. The older report-only scale and chaos suites are not substitutes for these correctness gates. diff --git a/docs/quality/RUNTIME_IMAGE_PACKAGING_2026-10-27.md b/docs/quality/RUNTIME_IMAGE_PACKAGING_2026-10-27.md new file mode 100644 index 00000000..3efcbbb7 --- /dev/null +++ b/docs/quality/RUNTIME_IMAGE_PACKAGING_2026-10-27.md @@ -0,0 +1,126 @@ +# Runtime dependency packaging — 2026-10-27 + +This implements the runtime-packaging slice of #185, based on reviewed toolchain +source `569ad9f`. It does not publish or deploy images. Main/release gates, hosted +acceptance and base-OS triage remain separate. A green inventory step proves a +scan completed; it does not mean every recorded finding has been cleared. + +## Installed runtime contract + +API, generic and self-host API builds install the lock's selected production +workspaces (core/server/MCP plus root production declarations), omitting dev and +optional packages and install scripts. Each build compiles SQLite against its +own image libc and copies that exact native binding into the production install. +Workspace-local dependency trees are retained. Compiled application output and +package manifests are copied; workspace source/test trees, compiled test files, Vite, +Vitest, TypeScript, tsx, ESLint, Turbo, esbuild, node-gyp and global npm/Yarn/ +Corepack are excluded. The generic image still includes the built web assets. + +`graphql-tag` was used directly by server schemas but declared only by the web +workspace. The reduced image exposed that missing declaration. The server now +declares the existing locked 2.12.6; no installed dependency version changed. +Root `passport-openidconnect` remains resolved through the explicit root install. +The API no longer shells out to `npm run create-admin`: the awaited SQLite +initialization already creates/checks the initial account before Neo4j startup. + +Existing runtime identities remain API root (legacy behavior), generic +`graphdone:1001`, and self-host `node:1000`. This does not claim API non-root +hardening. Generic's existing UID is preserved for existing data owners. +Self-host now defaults to `NODE_ENV=production` even outside Compose. Production +HTTP/HTTPS Compose auth volumes target the actual server working directory, +`/app/packages/server/data`; the previous `/app/data` target did not persist the +store used by the process. No existing volumes are migrated or deleted here. + +## Acceptance and negative evidence + +Three clean final images passed `tests/runtime/run-image.mjs IMAGE EXPECTED_UID`. +The runner checks the actual image configuration/default command, missing-secret +failure without network access, installed consumer resolution (including SDK +subpaths/root imports), absent tools/tests, runtime UID and native SQLite. + +Each image then starts its compiled API against an explicitly owned loopback +Neo4j fixture and private named auth volume. The test rejects missing/replayed +CAPTCHA, solves a real production challenge, signs in, creates an owned graph, +uses the compiled MCP stdio process to create/read a node, observes it through +GraphQL, updates it through GraphQL and verifies MCP plus independent database +readback. Protocol errors and cleanup failures fail. Replacing the container +while retaining only its named volume must preserve the exact SQLite account ID. +The entire real boundary contract passes again after replacement. Owned graphs, +containers and named volumes are removed strictly by the runner. + +Retained failures: + +- The old full-dependency API image fails the actual installed-tool inventory. +- The first reduced image fails real ESM startup on missing `graphql-tag`. +- Preliminary review caught the shell-based administrator check and proposed + generic UID drift before acceptance. The final source removes the redundant + shell call and preserves existing identities. +- Initial inventory drafts incorrectly treated `typescript-memoize` as the + compiler and resolved the MCP SDK's nonexistent root entry. The corrected + contract checks exact tool names and actual exported consumer subpaths; it + still launches the real compiled API/MCP processes. + +Local clean host install, all-package typecheck/lint and harness 192/192 pass. +The affected server suite passes 211/211. Docker builds compile all relevant +packages. Existing lint/bundle warnings remain. These checks use owned Linux +fixtures, not a public deployment, Windows test or historical-volume migration. + +## Actual image and scanner provenance + +Final local manifest digests: + +| Image | Manifest digest | Runtime UID | +| --- | --- | --- | +| API | `sha256:4deb93462ed64371cc64a425f099449ad7561f3e4df2a46f336658b44caa8a00` | 0 | +| Generic | `sha256:43202783142bbde44333518f451e39b67495a06ffa25ee783871f7c9c81b4535` | 1001 | +| Self-host API | `sha256:8d464a8965e84b815e0c5172ccac982b9942a50b07a474e5dac83ce72c637ea4` | 1000 | + +The inventory records 393 named manifests per image, including module entrypoint +manifests without versions; this is not a distinct-package or exploit count. +Trivy reports 377 Node package entries per image. It also records 28 Alpine OS +packages or 88 Debian OS packages. Separate counting schemes are not conflated. + +Scanner: Trivy 0.76.1, immutable scanner image +`aquasec/trivy@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c`. +DB version 2, updated2026-10-27T19:17:44.708952708Z and downloaded23:01:37Z. +Scanner containers receive only the owned image archive and dedicated cache; +no Docker socket, credentials or broad workspace mounts are required. + +Commands use `docker save` and `trivy image --input ... --scanners vuln +--list-all-pkgs --format json`. Full JSON and DB metadata are retained under the +owned dated evidence directory and by the new required CI image matrix. Scans +are inventories of known findings in supported analyzers, not proof of absence +of vulnerabilities or of package reachability. + +| Final scan | OS findings | npm findings | +| --- | ---: | ---: | +| Alpine API | 0 | 0 | +| Alpine generic | 0 | 0 | +| Debian Bookworm self-host API | 228 | 0 | + +Bookworm initially reported 229 OS entries. Upgrading tzdata from 2026c to +2026e-0+deb12u1 removes the one entry with an available distro fix. The final raw +inventory retains 1 critical, 56 high, 87 medium, 83 low and 1 unknown; statuses are +170 affected, 17 will_not_fix and 41 fix_deferred. These counts are not distinct +reachable exploits. Base-OS triage is tracked separately under #209. + +The critical raw entry, CVE-2023-45853 on zlib1g 1:1.2.13.dfsg-1, concerns MiniZip. +[Debian's primary assessment](https://security-tracker.debian.org/tracker/CVE-2023-45853) +states the affected contrib/minizip component is not built for Bookworm's package. +Actual `dpkg-query -L zlib1g` lists libz.so.1.2.13; no minizip/libminizip1 package +is installed. This specific applicability evidence does not dismiss other +findings or alter the raw scan. [Debian tzdata inventory](https://security-tracker.debian.org/tracker/source-package/tzdata) +identifies the installed security update. + +## CI and remaining limits + +The required `Runtime Images` matrix builds each final image and runs the same +real contract, including replacement-container recovery. `CI Success` rejects +failed/skipped matrix outcomes. The inventory step requires valid OS and Node +package evidence and retains all findings; it is deliberately not represented +as a zero-vulnerability policy. Final exact-head review and CI remain required. + +Turbo/Mermaid tooling updates, base-OS remediation/applicability review, historical +mis-mounted data recovery, hosted acceptance and main/release/deployment clearance +are unfinished. Existing TLS mount semantics are preserved; these local image +contracts exercise HTTP API behavior and do not replace production TLS acceptance. diff --git a/docs/quality/RUNTIME_VALIDATION_2026-10-23.md b/docs/quality/RUNTIME_VALIDATION_2026-10-23.md new file mode 100644 index 00000000..af3bd2c4 --- /dev/null +++ b/docs/quality/RUNTIME_VALIDATION_2026-10-23.md @@ -0,0 +1,46 @@ +# Runtime upgrades must be tested through startup and owned browser fixtures + +Date: 2026-10-23. Tracks GraphDone/GraphDone-Core#156 and the audit integration in #157. Baseline source: `a6d2d4138daf31fb07d8727fc33779a9bfdb1404`. + +## Findings and corrective rules + +Production `npm audit --omit=dev --json` initially reported 9 high and 1 critical advisory. Compatible lockfile updates remove vulnerable transitive versions; Apollo Server moves from its retired v4 line to v5 with the supported Express 4 integration. The pinned `@graphql-tools/utils` 12.1.0 override patches nested instances, including Neo4j GraphQL dependencies. That dependency family requires a newer Node runtime: the project now requires Node 22.18+, with CI on the minimum and Node 24, and Node 22 containers/installers. + +A build was insufficient evidence of runtime compatibility. After the initial install, the API failed to start because the hoisted Express integration could not resolve a peer installed inside the server workspace. Declaring the shared Apollo dependency at the root fixes resolution with the existing legacy-peer-deps installation policy. Validate a clean lockfile install, real API startup, and browser flows after dependency changes. + +The existing MCP CI contract job failed because it lacked the explicit disposable-database marker required by its destructive test guard. Set that marker only for the job-owned Neo4j service. Never set it for shared developer or production databases. + +The first headed smoke run passed 4/5 cases: the rendering case assumed the default graph contained nodes. The screenshot showed a valid empty Welcome graph. Rendering and layout cases now create their own two-node, one-edge fixture and delete only that fixture afterward. Required grow/layout assertions no longer silently skip, and navigation failures are not swallowed. Empty-graph behavior remains separately exercised. + +## Recorded validation + +Commands below ran in an isolated worktree on Node 22.23.4, with a separately owned Neo4j 5.26.12 container on ports 17474/17687 and a development API/web stack on 14127/13127. Credentials were generated locally and are omitted. + +| Command/check | Observed result | +| --- | --- | +| `npm ci --legacy-peer-deps` | Clean install succeeded; final lockfile install repeated before review. | +| `npm run test` | 434 passed across core/web/server/MCP; 25 DB-gated cases skipped in the default MCP run. | +| MCP `RUN_DB_CONTRACT=1 GRAPHDONE_DISPOSABLE_NEO4J=1 npx vitest --run tests/neo4j-contract.test.ts` | 8/8 passed against the owned real database. | +| `npm run build` and `npm run lint` | All packages built; zero lint errors, existing warnings remain. | +| API `/health` | API started and Neo4j reported healthy after peer-resolution correction. | +| Headed Chromium `tests/e2e/smoke/user-smoke.spec.ts --workers=1 --retries=0` | 5/5 passed with owned fixtures (1.5 minutes). | +| `docker build -f packages/server/Dockerfile` | Fresh Node 22 Alpine lockfile install and API/core build succeeded. | +| `npm audit --omit=dev --json` | 0 high, 0 critical, 8 moderate as of 2026-10-23. | + +Local ignored logs live under `test-artifacts/audit-20261023/`; public PR checks and review comments carry subsequent commit-specific results. These local results precede the other audit branches and must not be presented as integrated or deployed validation. Full dependency audit still includes development-tool advisories; production audit alone is not a security audit. Container build does not prove a deployed image works. Installer changes were syntax/config checked, not installed onto another person's machine. + +The first remote Node 22 run caught an existing harness command incompatibility: `node --test tests/lib/` treats the directory as a module on Node 22. A quoted recursive test-file glob runs all 105 cases successfully. Both minimum-Node and Node 24 CI matrix jobs now execute the harness tests too. Static lint and typechecking had passed; the failed harness invocation was not counted as a pass. + +## CI requirements + +All PR bases now trigger CI. Locked installs replace mutating installs. Unit jobs build their workspace dependency first, the harness library regressions run in CI, and smoke runs through the strict evidence adapter. High/critical production dependency advisories now block CI Success. The broader headed new-user, mobile, graph, and performance gates remain enabled. Protected branch approval requirements remain in force. + +References: [Apollo Server migration](https://www.apollographql.com/docs/apollo-server/migration), [Node release schedule](https://nodejs.org/en/about/previous-releases). See [testing guidance](../testing/README.md) for evidence limits and [quality guidance](RUNBOOK.md) for user-level validation. + +## Independent launcher review + +A command-extraction negative control showed `ensure_nodejs` returning failure while its callers continued into a build because these launchers deliberately disable `set -e`. Every caller now propagates failure before starting work. Dashboard scripts also stop before service/cron mutations unless PATH supplies a supported runtime; the hardcoded Node 20 fallback is removed and their self-check uses the compatible test-file glob. No user service was installed or restarted during these checks. + +Both API image variants subsequently started with generated credentials against the owned database and returned HTTP 200 with Neo4j healthy. The first container probe could not reach a loopback-only database through the bridge; using host networking with distinct owned ports resolved that fixture issue. The optional MCP subsystem remained offline and is not claimed healthy. + +The headed new-user CI gate also runs through the strict adapter as `--sequence new-user`: every existing new-user spec remains selected, one headed worker runs with zero retries, and skipped/unexecuted/flaky/global-error evidence fails the gate. Direct local `qc:new-user` remains useful for diagnosis; its raw exit code alone is not substituted for the strict CI report. diff --git a/docs/quality/SCENARIOS.md b/docs/quality/SCENARIOS.md new file mode 100644 index 00000000..eae20208 --- /dev/null +++ b/docs/quality/SCENARIOS.md @@ -0,0 +1,58 @@ +# End-to-end scenario catalog + +Use needs and context, not assumptions about age, gender or technical ability. A parent can also be an engineer; a scientist may be a first-time user. Every actor needs a clear outcome with minimal bookkeeping. + +## Standard scenario format + +Each scenario records: **ID; actor(s) and permissions; team size; goal; preconditions and data; device/input; ordered action → expected result steps; recovery/adversarial variants; cleanup; automation/evidence; result and remaining gaps.** + +The machine-readable single-actor cases are in `tests/fixtures/quality/personas.json`; `persona-lifecycle.spec.ts` executes their actions. Setup creates an isolated empty graph using the API; task creation, notes and status changes use the UI; independent API readback and reload verify persistence. Core currently uses the seeded development ADMIN test account (a MEMBER fixture is not available); local D1 uses an ephemeral guest. This is not evidence of multi-user collaboration or scale. + +| ID | Actors / context | Outcome and distinct risk | Main journey | Recovery / adversarial variant | Gate evidence | +| --- | --- | --- | --- | --- | --- | +| HOME-01 | Parent, individual household organizer | Prepare a school trip without project-management jargon | Create → note → complete → return on phone | Interrupt work; rotate; return without duplicate tasks | Automated lifecycle + responsive gate | +| STUDENT-01 | Student in a group of 4 | Prepare a reproducible project demo for review | Create → record evidence → request review → reload in landscape | Teammate disagrees; return to active work without losing notes | Automated single-actor lifecycle; actual handoff required below | +| SOLO-01 | Independent engineer | Fix an inaccessible login form | Create → record verification → complete → inspect totals | Network fails during save; text retained and retry is safe | Automated happy path; failure recovery remains a release check | +| TEAM-01 | Community-event coordinator, team of 6 | Surface a venue/accessibility blocker before invitations | Create → explain blocker → mark blocked → inspect activity | Blocker clears; downstream work becomes understandable | Automated single-actor lifecycle; dependency journey required below | +| LARGE-01 | Release coordinator, organization of 60 | Request review with visible evidence and outstanding risk | Create release review → record evidence → request review → return | Simultaneous updates, restricted graphs, many projects | Automated single-actor lifecycle; does not simulate 60 users | +| RESEARCH-01 | Researcher, aerospace team of 12 | Reproduce a simulation with assumptions and units documented | Create → record reproducibility note → request review → reload on tablet | Invalid inputs; conflicting findings; independent reviewer rejects result | Automated single-actor lifecycle; no scientific validity claim | +| COLLAB-01 | Student author + peer reviewer, 4-person team | Complete an actual two-account handoff | Author creates → reviewer opens same work → comments → author revises → reviewer accepts | Viewer attempts a write and is rejected server-side | BLOCKED until two-account UI and authorization evidence exists on target backend | +| COLLAB-02 | Coordinator + volunteer, 6-person team | Unblock dependent work | Create prerequisite/dependent → connect → assign → volunteer completes prerequisite → coordinator verifies | Wrong connection → remove/undo → reload integrity | Core grow/undo smoke and D1 edge contract are partial; full handoff BLOCKED | +| COLLAB-03 | Two engineers editing concurrently | Preserve both users' intentions | Open same task in two sessions → interleaved updates → refresh | Conflict detected or reconciled; no silent lost update | BLOCKED: concurrency policy and full journey not verified | +| LARGE-02 | Admin, team lead, editor, viewer, outsider | Keep private work private at larger scale | Create separate teams/graphs → switch contexts → authorized edit → unauthorized read/write attempts | Stale access after role removal, guessed IDs, cross-graph links | D1 cross-account CRUD isolation partial; membership/role matrix BLOCKED | +| LARGE-03 | 60 users across 8 projects | Find useful work quickly under realistic volume | Seed 1,000 tasks across owned projects → search/filter → edit → return | Pagination, quotas, slow network, bounded D1 reads | BLOCKED pending measured load/interaction budgets; never seed production | +| RESEARCH-02 | Analyst + independent reviewer | Preserve evidence and distinguish a note from an audit trail | Record model/version/units → attach references → review → correction | Editable notes must not pretend to be immutable evidence | Activity explicitly distinguishes notes; full evidence workflow BLOCKED | +| AI-01 | Human coordinator + API-writing AI contributor | Observe genuine progress without agent-management clutter | Human opens work → API records progress → human sees update → returns later | Failed API update; duplicate delivery; spoofed actor | Core recorded-AI and local D1 progress checks partial; verified attribution/idempotency BLOCKED | +| AI-02 | Human approver + managed autonomous agent | Delegate with clear ownership and approval boundaries | Request outcome → agent claims → progress → review → human accepts | Lease expires, agent fails, duplicate claim, unauthorized action | DEFERRED to managed product; Core must not claim autonomous execution | +| ACCESS-01 | Keyboard-only or screen-reader user | Create, edit and recover without pointer/hover | Navigate → open → edit → close → restore focus | Zoom, forced colors, reduced motion, mobile keyboard | Keyboard/rotation checks partial; native AT evidence BLOCKED | +| ACCESS-02 | Any user on intermittent mobile data | Keep work through interruption | Edit → disconnect → save → reconnect → retry → reload | Expired credential, duplicate request, unsaved navigation | BLOCKED until failure and recovery assertions pass | +| ENTRY-01 | First-time guest → returning account holder | Know what saves and how long it lasts | Visit deep link → try guest → create → return/sign in | Expired guest, unavailable email provider, disabled guest writes | Layout and D1 persistence partial; remote account flow BLOCKED | +| RELEASE-01 | Returning user after deployment | Open bookmarked work on the exact live build | Static deep link → assets → intended API → saved graph | Old cached HTML, migration rollback, API unavailable | Staged artifact gate + release ledger; remote verification required | + +## Motion and graph-first interaction gate + +**MOTION-01:** Any user arranging their work in Graph view. Use pinned tasks with an active status at 390×844, 844×390 and 1440×900. Verify a real pointer drag moves the saved coordinates by more than 20 graph units (a missed drag must fail). A fully placed graph must return to rest within two seconds of release. Change type through the visible editor, rotate an open phone graph, then sample every animation frame for 17 seconds, crossing the normal poll interval. Node anchor positions must span no more than one screen pixel on either axis. Record samples and screenshots. Measure translation independently of intentional glow/scale effects. + +`npm run qc:motion` executes this gate; it is also included in `qc:new-user`. Settling unplaced/dense graphs, connected-node dragging, edge-label shaking and rapid create/delete/switch sequences require additional cases; the pinned-node baseline alone must not claim complete animation coverage. + +**MOTION-02:** Resize a loaded graph from 1000×700 through small desktop increments up to 1920×1080, shrink it again, rotate between 390×844 and 844×390, and expand back to desktop. After every change, the card bounds must be centered within three pixels and remain inside the graph viewport. Saved node coordinates must remain identical. `graph-resize.spec.ts` runs headed in `qc:motion`, `qc:graph`, and `qc:new-user`. This specifically catches incremental resizing below the former 20% threshold. + +**GRAPH-FIRST-01:** Create a new private graph through its UI; create a first task; drag it; grow and name a connected task; edit details and progress; change a connection; undo; reload. Keep Graph view selected throughout. Record the actual UI actions and saved result. Use a disposable named graph in local development and retain a screenshot per important step. The hands-on pass uses the visible headed browser; API calls may verify results but may not substitute for these UI actions. + +## Execution and coverage rules + +- Run `TEST_URL=http://localhost:3127 npm run qc:personas` for Core, and `QC_BACKEND=cloud QC_API_URL=http://localhost:8797/api/graphql TEST_URL=http://localhost:3129 npm run qc:personas` for local D1. Both run headed and serial. +- The six automated fixtures deliberately cover completion, blockage, review, portrait, landscape, tablet and desktop. Pair them with all-mode tests, guest/accessibility checks, relationships and account-isolation tests. +- A renamed task is not proof of a different team's workflow. The collaborative, concurrent, permission and scale scenarios above require distinct actors and assertions before passing. +- Record backend, source revision, scenario ID, device, evidence path, result and limitation in `RELEASE_QC.md`. Do not turn BLOCKED into PASS because a related single-user test passed. + +## AGENT-COLLAB-01 — a human and their agent share a project + +- **Persona:** a parent planning an event, a student coordinating a project, or an engineer preparing a release; repeat with realistic titles and priorities. +- **Environment:** disposable owned Core project; 390×844, 844×390, 1440×900; headed Chromium. Cloud contract uses isolated local D1 account. +- **Goal:** delegate a review, see progress and a reply, then remove access without leaving the graph. +- **Sequence:** open Your agent → name and create collaboration token → privately copy instructions → human leaves a project note → agent reads context → updates a task's priority/status → leaves a task note → human sees attributed reply → revoke → confirm agent is denied → create read-only token → confirm reading works but writing is denied → revoke and close with Escape. +- **Expected:** same persisted task appears in the graph; notes identify human/agent using server authentication; no login token is shared; no credential can cross projects or create more credentials. Closing the panel hides the raw token. Panel remains within the viewport. +- **Negative cases:** nonexistent task in note, priority outside 0–1, wrong project, read-only write, revoked access, forged author field; local D1 additionally forces expiry. +- **Automation:** `qc:agents`; Cloud `scripts/test-agent-connections.mjs`. No autonomous model execution is simulated or claimed. +- **Remaining variants:** separate collaborating human accounts, large note history, native mobile keyboard, hosted browser staging, provider-specific agent connection instructions. diff --git a/docs/quality/SCENARIO_RESULT_TEMPLATE.md b/docs/quality/SCENARIO_RESULT_TEMPLATE.md new file mode 100644 index 00000000..26f62ef0 --- /dev/null +++ b/docs/quality/SCENARIO_RESULT_TEMPLATE.md @@ -0,0 +1,19 @@ +# Scenario result + +- Scenario ID / variant: +- Source revision / backend / environment: +- Actors and actual permissions: +- Team size and seeded workload (do not confuse context with simulated load): +- Device, viewport, input, assistive technology: +- Goal and preconditions: + +| Step | User action | Expected UI and durable state | Observed result / evidence | +| --- | --- | --- | --- | +| 1 | | | | + +- Outcome: PASS / FAIL / BLOCKED / NOT APPLICABLE +- Action count to outcome (exclude fixture setup): +- Recovery / authorization / concurrent-editor variants exercised: +- Cleanup verified: +- Screenshot / trace / API readback / test log: +- Remaining gaps and release impact: diff --git a/docs/quality/SCHEMATIC_ROUTING_VALIDATION_2026-10-28.md b/docs/quality/SCHEMATIC_ROUTING_VALIDATION_2026-10-28.md new file mode 100644 index 00000000..8caf401d --- /dev/null +++ b/docs/quality/SCHEMATIC_ROUTING_VALIDATION_2026-10-28.md @@ -0,0 +1,180 @@ +# Schematic routing validation — 2026-10-28 + +Candidate continuation of #216, #218 and #219, based on frozen nested source +`a311154bf154af977a238d4b5de55773efa4e2dc`. Final accepted source/review/CI identities +belong in the PR. This is not a full application acceptance claim. + +## Small local checks + +- Four new relationship component cases failed against the unchanged a311 + overview at the missing all/internal/endpoint/neighborhood controls. Retained: + `/tmp/graphdone-resolution-20261027/relationship-component-negative.log`. +- The candidate's focused relationship model/components and existing nested + component regressions passed 16 cases with one worker and no file parallelism + in 4.36 seconds. This includes source identity, 100+ degree hub, directions, + two-hop traversal, missing endpoints, inspection return, removed-row focus and + no position scan/lane shuffle for camera pan. Log: `schematic-scoped-components.log`. + The final bottom-mounted Connections control was subsequently repositioned to + avoid adding another row to the phone toolbar; final CI must cover that source. +- Six pure geometry cases passed in about 0.3 seconds on Node 22.18.0. The explicit + spaghetti fixture is rejected; accepted chain/fan-out/dense/phone fixtures + conserve all original IDs, retain a useful visible minimum and have zero card + intersections, unrelated contacts/overlaps or diagonal segments. Input reorder + preserves lanes. Log: `schematic-routing-bounded.log`. +- The actual unified CLI child/manifest regression suite passed 31 cases in + 4.14 seconds, including strict schematic sequence/config/CI enrollment. Log: + `schematic-cli-enrollment.log`. +- Web typecheck passed in the owned component probe after supplying the built + core dependency. The first attempt correctly failed because that clean-installed + probe lacked `@graphdone/core` build output; its failure is retained in + `schematic-scoped-typecheck.log`. Core tracked source was proven unchanged + between the probe base 43ada01 and a311, and unmodified in the probe; the existing + validated a311 core output was copied. Corrected log: + `schematic-scoped-typecheck-with-core.log`. This is scoped type evidence, not a + substitute for CI's clean build of the complete candidate. + +All named logs are in the retained directory above. No local browser or database +was launched for these checks after the owner's workstation constraint. + +## Visual inspection and limits + +The [static contact sheet](schematic-routing-study/index.html), four SVGs and +[metrics](schematic-routing-study/metrics.json) use the same pure router now wired +into the overview. SVGs were rasterized with installed `rsvg-convert`; fan-out, +dense and phone images were inspected. That inspection caught a phone title +running outside its card; word wrapping was adjusted before retaining the final +artifacts. A real SVG text-bound/overlap check now lives in the CI-only schematic +browser sequence. Its four required cases and screenshots are pending actual CI; +static raster inspection is not a passing browser test. + +The dense fixture contains 112 relationships: 33 are represented by 13 routed +branches and 79 are explicitly collapsed. This is honest accounting and a clean +initial overview, not completed dense-graph exploration. Named focused-bus tracing +is still required so each selected area's full relationships can be followed. +Item-level D3 wiring still uses its earlier geometry. Neither 1k/10k performance, +hosted parity nor release/deployment is established by these small fixtures. + +## Broad gates + +The prior a311 CI and Comprehensive workflows passed, with complete exact-source +reports recorded in #216. Their results do not accept the new router/components. +The new candidate requires independent exact-source review, clean CI unit/build/ +type/lint/runner checks, the new four-case static visual sequence and existing +strict graph/layout/mobile/new-user/database/runtime gates. Heavy local matrices +remain stopped. User-directed interruptions and earlier D-Bus failures are kept +in the linked [workstation lesson](../../.agents/inbox/2026-10-28-browser-acceptance-needs-an-owned-session-bus.md). + +## Independent review corrections and retained CI failure + +Review of a04fa2b found stale member-ID snapshots in open group/bundle lists and +a routing allowance that reset for each failed search. Four new component cases +and an actual failed-search allowance case failed before correction. Stable +hierarchy selectors now resolve live membership; removed groups are explicit. +One invocation-wide attempt/state budget covers successful and failed searches. +The Connections count includes its internal, pending and deferred partitions. + +Corrected scoped results: 22 component/model cases passed in 5.53s; 8 geometry/budget +cases passed in 0.24s; web typecheck passed. Earlier negatives are retained. See the +[canonical correction lesson](../../.agents/inbox/2026-10-28-bus-views-need-live-membership-and-total-budgets.md). +The dense fixture now routes 33 original edges through 13 branches, explicitly +collapses 79, and records 32 attempts/3,495 examined states. This remains a partial +overview pending focused tracing; the fixture was not reduced. + +Actual a04 CI 40629282719 failed the new visual gate: 3 of 4 cases passed, while the +phone Delivery label entered its required padding by 0.265625px. Its trace and +screenshot are retained. The generator now wraps more narrowly without changing +font, card size, assertion tolerance, timeout or retries. The revised four-case +browser acceptance and broader candidate CI remain pending. Prior green jobs +and Comprehensive 40629282743 do not accept the revised source. + +## Actual overview integration gate + +The existing desktop/phone nested journeys now measure the actual rendered paths +and card/control rectangles, require visible routes and exact represented/hidden +accounting, and retain an overview screenshot plus geometry JSON. They also browse +the real owned graph's full relationship IDs, filter parallel BLOCKS links, inspect +an endpoint and return with camera/filter/focus preserved, then verify incoming, +outgoing and two-hop neighborhoods against the original database snapshot. Existing +write-attempt guards and final database equality remain required. This reuses the +two small owned fixtures; no extra database or local browser is started. + +The final two cases were discovered successfully in the owned lightweight probe +(`schematic-integration-current-discovery.log`); discovery is not execution. Their +new assertions require actual CI execution before acceptance. + +Independent review of 41127f0 found ambiguous long-title selectors after adding +relationship buttons, and a geometry oracle that could accept transparent wires. +The member selectors now use their visible title text. Cards must have positive +visible bounds inside the viewport; wires must have visible ancestor styles and +actual stroke paint. An intentional opacity-zero wire in the real journey must +be rejected before restoration. These corrections remain pending CI execution. + + +## Focused group buses + +The candidate adds incoming/outgoing group tracing with at most five neighbors +per page and fewer on constrained viewports. Every page retains the selected +group; all source relationships remain counted and available in the complete +list. Incoming routing shares the target trunk while preserving actual direction. +A newly mounted trace control is measured after commit as an obstacle. + +Retained failing-before evidence: + +- `schematic-incoming-negative.log`: 8 passed, 1 failed; the old router did not + produce incoming shared-trunk junctions. The corrected 9 cases passed in 0.28s + (`schematic-incoming-positive.log`). +- `schematic-trace-component-intended-negative.log`: 11 passed, 2 failed at the + missing trace controls. The first command used the repository root instead of + web's configured test environment and failed with `document is not defined`; + `schematic-trace-component-negative.log` is retained as a setup failure, not + feature-negative evidence. +- `schematic-trace-components-final.log`: 28 passed, no skips, 8.31s, one worker + with no file parallelism. Includes paging every neighbor, live source removal, + incoming/outgoing counts, tiny viewports, current-page clamping, original bundle + identity, inspection return and existing nested/relationship regressions. +- `schematic-trace-typecheck.log`: scoped web typecheck passed. + +Actual CI now also traces both directions in the existing two owned nested +fixtures, checks original source/target port placement and arrow presence, and +includes the trace controls among wire obstacles. Those browser assertions are +pending; no local browser/database or additional fixture was started. This is +not detailed item wiring, arbitrary selected-path tracing, a scale benchmark, +or accepted full application behavior. + +## Issue closeout coverage audit + +Independent review against the live #215/#218/#219 acceptance bodies found that +#218's self-loop/high-degree hub, actual keyboard navigation, and direct internal/ +bundle entrypoints needed real browser coverage beyond the existing model tests. +The same 72-node nested fixture now adds a 71-neighbor hub and one self-loop in +20-edge batches. API readback verifies every hub neighbor and exactly one loop. +The journey compares the internal and directional BLOCKS bundle lists against +API-derived edge IDs, uses Tab/Enter for desktop relationship inspection/Back, +and preserves existing phone touch actions and final unchanged-data/write guards. +Node count, fixture/test timeouts, one worker and zero retries are unchanged. + +This is a focused test-only completion of the issue criteria, pending exact-source +CI. It adds no local database/browser workload. #219 defines the first group-routing +slice; detailed item wiring remains #220 and full LOD remains #193. No issue may be +closed before its accepted exact-source gates and post-integration dev CI. + +The first extra-edge implementation (2df89e0) failed source review because its +batches ran outside owned seeding's cancellation/drain boundary. The corrected +fixture passes those edges into the existing signalled batch loop. Its browser-free +ownership regression passed 4 cases in 6.07s after a retained missing-support +negative; no browser/database launched. See the +[fixture lesson](../../.agents/inbox/2026-10-28-fixture-extras-must-share-cancellation.md). + +Actual CI40633460829 on source88d0115 passed static visual4, smoke6, new-user42 and +semantic-LOD2 with clean unchanged merge025288dafa5fcbefb282fc2c5a313a58dcddebd2 source, +no warnings/skips and successful execution. Nested-LOD failed both cases at exact +relationship-type label lookup; initial geometry/paint checks had executed, but the +journeys are incomplete. Layout/mobile/performance stages were skipped downstream. +Explicit accessible names correct the three select controls without relaxing +locators or timeouts. Scoped relationship components passed 13 cases in 5.48s. + +PNG/JSON evidence now uses owned file-path attachments with separate overview, +outgoing and incoming names, matching the existing unified adapter. Prior body-only +attachments were omitted from its normal report. The next actual browser report +must contain the screenshots and geometry before visual acceptance. All current +corrections and the larger hub fixture still require exact-source CI. diff --git a/docs/quality/SECURITY_SCOPE_2026-10-23.md b/docs/quality/SECURITY_SCOPE_2026-10-23.md new file mode 100644 index 00000000..6f5a9fd4 --- /dev/null +++ b/docs/quality/SECURITY_SCOPE_2026-10-23.md @@ -0,0 +1,108 @@ +# Security coverage and remaining work from the October 23 sweep + +This is a bounded source/dependency review of Core snapshot `6b2a007f8810c4ec08fccb3df83cc01cd44804d7`, plus the scoped lint dependency correction described below. It does not establish the security or deployed revision of graphdone.com. Folder authorization ([PR #160](https://github.com/GraphDone/GraphDone-Core/pull/160)), queued-save ownership ([PR #158](https://github.com/GraphDone/GraphDone-Core/pull/158)), and test evidence ([PR #159](https://github.com/GraphDone/GraphDone-Core/pull/159)) have separate, narrower evidence. + +## Dependency inventory and reachability + +On 2026-10-23, Node 22.23.4/npm 10.8.2 ran `npm audit --package-lock-only --json` against the snapshot's unchanged manifests and lockfile in a temporary directory. It reported **23 affected package entries: 3 critical, 7 high, 11 moderate, 2 low**. `npm audit --omit=dev --json` reported **8 moderate, 0 high, 0 critical**. These are package-tree counts, including inherited findings, not 23 distinct exploitable defects. Earlier counts of 22 are not the result of this dated run. + +| Family | Observed surface | Priority and next validation | +| --- | --- | --- | +| React Router 6.30.6: two production entries | Browser runtime. Inspected `Link`/`navigate` destinations are literals or fixed navigation arrays. The app uses `createRoot` + `BrowserRouter`, not server hydration. No attacker-controlled navigation path or SSR sink was found in that search. | **P2:** migrate through a dedicated router PR to a patched release, testing redirects, route guards, login callbacks, and mobile navigation. The [navigation advisory](https://github.com/advisories/GHSA-wrjc-x8rr-h8h6) still applies to the installed version; source inspection is not proof that every future caller is safe. | +| UUID 9.0.1 and two inherited Apollo federation entries | Server dependency. Inspected application imports call `v4()` without an output buffer. The [advisory](https://github.com/advisories/GHSA-w5hq-g745-h8pq) concerns buffered v3/v5/v6 calls; no such application call was found. | **P2:** review a UUID update to at least 11.1.1 and compatible upstream federation updates; test CJS/ESM startup, schema generation, and generated IDs. Do not force the audit-suggested latest major across every consumer without that review. | +| Tailwind 3.4.19, postcss-nested, selector parser: three production-classified entries | CSS build pipeline (`packages/web/postcss.config.js`), not an observed request-time CSS parser. Tailwind is currently declared in production dependencies. | **P2:** resolve the [selector parsing advisory](https://github.com/advisories/GHSA-rj75-hqrm-r3gf) through a compatible upstream update or a separately validated migration. Moving a declaration to devDependencies changes the count, not the vulnerable code. Validate generated styles and responsive views. | +| Vite/vite-node/esbuild: three additional entries, including one high | Development server and test tooling. `packages/web/vite.config.ts` binds Vite to `0.0.0.0`; access depends on deployment/network configuration. Production web Docker uses built assets in nginx. | **P1 for exposed development servers:** isolate access and upgrade Vite in a dedicated toolchain PR, including editor endpoints, path handling, and dev proxy checks. Several [editor endpoint conditions](https://github.com/advisories/GHSA-r825-w8mm-5pxw) depend on OS/editor configuration. No live attack or deployed exposure was tested. | +| Vitest/coverage/tinypool: three additional critical entries | Test execution. Configured commands use `vitest --run`; no Vitest UI/API listener configuration was found. The [Vitest issue](https://github.com/advisories/GHSA-5xrq-8626-4rwp) requires UI/API/browser-mode conditions; [tinypool](https://github.com/advisories/GHSA-85c8-ppgw-ccpr) requires a prototype-pollution prerequisite. No application import of these tools was found. | **P2, P1 if exposed UI is used:** upgrade Vitest, coverage, and worker dependencies together. Validate real suite discovery, reporters, skips, worker cleanup, and coverage; do not replace the worker major beneath Vitest 1 without compatibility evidence. | +| Root TypeScript-ESLint/minimatch: six additional high entries | Lint path/glob processing, not a public application API. Root parser 6.21.0 pins minimatch 9.0.3. | **Corrected in this follow-up:** narrowly override this parser's minimatch to 9.0.7. The [nested-extglob issue](https://github.com/advisories/GHSA-23c5-xmqv-rm74) was reproduced in a bounded child process. | +| Turbo: one additional moderate entry | Local build/test orchestration. The repo uses npm; no Turbo remote-cache login flow was reviewed. | **P2:** separate Turbo migration, including pipeline configuration and cache provenance. Its [repository-detection issue](https://github.com/advisories/GHSA-3qcw-2rhx-2726) concerns local tool execution; it is not evidence of a public web route. | +| Mermaid/KaTeX: two additional low entries | Mermaid is loaded by the project-transfer browser test. Application code exports diagram text; no application Mermaid renderer was found. | **P3:** update the test renderer in a scoped PR and rerun diagram export/render tests, retaining strict rendering settings. | + +The server and generic Dockerfiles currently install/copy development dependencies too. A development-classified package can therefore exist in a server image without being loaded by the API. The production-only audit gate does not audit the complete image or establish runtime reachability. + +## Narrow correction and checks + +Correction commit: `1a53279cdb96130f5c3e0af93a6502964a48ab1f`. The override applies only to `@typescript-eslint/typescript-estree@6.21.0`; other parser and matcher majors remain unchanged. The lockfile changes only that matcher and its brace-expansion dependencies. A clean `npm ci --legacy-peer-deps` completed on Node 22.23.4. + +- `tests/lib/dependencies/minimatch.test.mjs` uses the matcher's actual parser-relative resolution. The same test against 9.0.3 fails at its two-second child-process deadline; 9.0.7 completes and preserves positive/negative source-file selection. +- `npm run test:unified:lib`: **106 passed**, including the new dependency regression. +- `npm run lint`: **zero errors**, existing warnings remain. +- `npm run test`: **434 passed** across the four packages; **25 database-gated cases skipped**. The command also built all packages. This is the runtime snapshot's suite, not the combined folder/durability branch's larger suite. +- After correction, `npm audit --package-lock-only --json`: **17 entries: 3 critical, 1 high, 11 moderate, 2 low**. The six-entry lint chain is removed. Production findings remain the same eight moderate entries. + +## Remaining authentication work + +These are separate follow-ups, not fixes delivered by a dependency audit. Priorities describe remediation order, not independently assigned CVSS scores. + +| Priority | Evidence at the reviewed snapshot | Required fix and meaningful negative test | +| --- | --- | --- | +| **P1: CAPTCHA and password-login abuse controls** | `utils/captcha.ts:13` accepts any matching six-character string; an isolated direct call returned true for a synthetic value. `resolvers/sqlite-auth.ts:325` makes CAPTCHA optional on password login. `/graphql` has no password-login limiter; the REST email-request limiters are separate. The older localStorage lockout is a UI feature. | Require a server-verified challenge where policy calls for one, with bounded per-account/IP password attempts that also cover GraphQL aliases. Test direct API requests without browser state, arbitrary codes, and repeated aliased attempts. This is an abuse-control gap, not evidence that CAPTCHA alone grants an account session. | +| **P1: single-use magic links** | `auth/sqlite-auth.ts:1307` selects unused tokens, then queues an unguarded update at line 1313. A real in-memory SQLite probe verified the same fixture token concurrently: **2 successful verifications**. No shared DB was touched. | Atomically consume an unexpired, unused token and check the affected row before issuing identity. Require exactly one success under concurrent requests; test expiry, repeat use, account deactivation, and transaction failure. | +| **P1 when defaults are configured: signing/session secrets** | `utils/auth.ts:3` and `index.ts:151` retain known fallback secrets. No production startup rejection was found. Installer generation of secrets does not cover every launch path. | Reject missing/default secrets for production and validate installer/container/direct startup. No deployed secret values were inspected or reported. | +| **P2: token purpose and account lifecycle** | Login and password reset share `createMagicLink`/`verifyMagicLink`; reset consumes it at `index.ts:886`. Magic-link verification does not itself require the account to be active. | Bind tokens to purpose and validate current account state on each authentication path. Test reset-versus-login cross-use, disabled accounts, and revocation across password, magic-link, and OAuth sessions. HTTP exploitation was not exercised in this sweep. | +| **P2: OAuth account linking** | Configured strategies read provider email (`auth/oauth-strategies.ts:17,57,93`); `findOrCreateUserFromOAuth` links an existing email at `auth/sqlite-auth.ts:1148` without a verified-email precondition in that helper. | Review each provider's verified-email contract and require proof before automatic linking. Test unverified/missing email and an existing account. No live provider callback or account takeover was attempted. | + +Follow-up implementation on 2026-10-23: [PR #169](https://github.com/GraphDone/GraphDone-Core/pull/169), commit `7e91d1a59c9ae9051569d8ef711ef6619f921bd6`, adds guarded, awaited single-use consumption and inactive-account rejection. Its real SQLite negative controls failed before the fix; all eleven magic-link cases and 39 server tests pass afterward, including expiry during a delayed write, replay, and database errors. The expiry predicate uses SQLite execution time after a controlled delayed-write negative control exposed the earlier timestamp boundary. Independent review and integrated validation remain pending. The baseline observations above remain historical evidence; this proposed fix does not separate login/reset token purpose or establish general session revocation. + +Source locations are relative to `packages/server/src/` and anchored to the snapshot above. Local, secret-free evidence is under `/tmp/graphdone-residual-*-audit.json`, `/tmp/graphdone-minimatch-*.log`, and `/tmp/graphdone-residual-auth-probe.log`; the latter reports only `parallelMagicLinkSuccesses: 2` and `arbitraryCaptchaAccepted: true`. + +## CSS advisory reconciliation on 2026-10-27 + +The historical three CSS entries above remain the result of the October 23 +inventory. On October 27, the [official advisory](https://github.com/advisories/GHSA-rj75-hqrm-r3gf) +lists parser 6.1.5 and 7.1.6 as patched; affected ranges are `<6.1.5` and +`>=7.0.0 <7.1.6`. Its metadata was updated on October 26 and it is not withdrawn. +Live dev `8712389` already locks parser 6.1.5 for Tailwind 3.4.19 and +postcss-nested 6.2.0, the same parser resolution present in `36c02c0` on October 23. +The fresh-cache production audit has no CSS-chain finding. This follow-up does +not alter any dependency declaration, lockfile or production/dev classification. + +The dedicated guard checks every locked parser path and both actual CSS +consumers. Semantic tests preserve flat and nested selectors and generated +responsive Tailwind utilities; they are not a timing benchmark. This resolves +the named advisory for the current locked chain, without forcing a Tailwind +migration. Keep earlier audit counts as dated observations and do not infer a +cache cause. The [canonical lesson](../../.agents/inbox/2026-10-27-reconcile-advisory-backports-with-locked-consumers.md) +records commands, sources and limits. Final build/browser/CI evidence belongs +to the separate CSS review. Router, UUID and toolchain work remain separate. + +## Router correction on 2026-10-27 + +The dedicated #186 router change replaces Router/DOM 6.30.6 with 7.18.4, removes +the old `@remix-run/router` resolution and retains the existing declarative SPA. +Official advisories [GHSA-wrjc-x8rr-h8h6](https://github.com/advisories/GHSA-wrjc-x8rr-h8h6) +and [GHSA-337j-9hxr-rhxg](https://github.com/advisories/GHSA-337j-9hxr-rhxg) +identify 7.18.0 as fixed. Existing app routes use fixed destinations, with no +observed SSR hydration path; upgrading is not a claim that a live exploit was +demonstrated. The DOM compatibility package is retained to keep imports stable. + +The dependency regression checks every locked path and the actual web consumers +against both the patched range and their resolved lock entries. Navigation +controls reject external-looking mixed separators and preserve internal query, +fragment and history behavior; existing role guards remain covered. Six required +browser cases exercise public and authenticated deep links, reload, lazy pages, +desktop links/phone navigation, back/forward, role rejection and a fresh-context +login callback. The callback uses an owned password-issued token and does not +establish provider/email authentication. Initial negative and fixture-contract +failures are retained in the [dated lesson](../../.agents/inbox/2026-10-27-router-upgrades-need-current-consumer-contracts.md). +Fresh full-suite, browser, audit and final composition results are recorded on +the reviewable PR; UUID, CSS evidence, tooling and hosted acceptance remain +separate work. + +## Coverage still needed + +The folder tests do not establish all generated Graph/WorkItem/Edge permissions, concurrent deletion integrity, MCP/agent grant revocation, share-link scopes, session/cookie policy, uploads, backups, email delivery, or deployed proxy controls. Those need isolated contracts and user-level tests with current roles, multiple accounts, races, and negative cases. Dependency counts, passing smoke tests, and absence of a discovered source sink are separate pieces of evidence; none is a full security assessment. + +## UUID/federation follow-up on 2026-10-27 + +The #186 UUID correction starts from dev `ef495eff15ed7aac51e72dc7a1db1bb96d2b3695`. +Core's direct UUID 9.0.1 becomes ^11.1.1; federation-internals 2.14.4 has a +version-scoped UUID 11.1.1 override. Server deduplicates to that patched copy; +Mermaid retains 14.0.3. Subgraph/federation remain 2.14.4 after a 2.15.1 schema +compatibility failure. The [consumer lesson](../../.agents/inbox/2026-10-27-federation-consumers-bound-uuid-upgrades.md) +records negative controls, exact paths, clean install and four dependency +regressions. The pre-composition fresh-cache production audit had two moderate router +entries and high Undici. After composing merged dev #194 at `aba24a34d193b60e2318cd5b197f66e64f320215`, +a clean install and a second fresh-cache installed audit reported **two moderate +router entries, zero high/critical entries**, and no UUID/federation finding. +The earlier inventory above remains historical. No Tailwind declaration was reclassified. +This change does not close the router, CSS, dev-tool or hosted acceptance work. diff --git a/docs/quality/SELF_HOST_OS_TRIAGE_2026-10-28.csv b/docs/quality/SELF_HOST_OS_TRIAGE_2026-10-28.csv new file mode 100644 index 00000000..1ed5498d --- /dev/null +++ b/docs/quality/SELF_HOST_OS_TRIAGE_2026-10-28.csv @@ -0,0 +1,391 @@ +image,binary_package,installed_version,source_package,source_version,advisory,scanner_severity,scanner_status,scanner_fixed_version,release,vendor_status,vendor_fixed_version,vendor_no_dsa_reason,vendor_urgency,vendor_url +baseline,apt,2.6.1,apt,2.6.1,CVE-2011-3374,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2011-3374 +baseline,bash,5.2.15-2+b13,bash,5.2.15,TEMP-0841856-B18BAF,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0841856-B18BAF +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53613,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53613 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53615,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53615 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-76642,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78408,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78409,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78410,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-13595,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-13595 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-27456,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27456 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-3184,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2022-0563,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +baseline,bsdutils,1:2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2025-14104,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-14104 +baseline,coreutils,9.1-1,coreutils,9.1,CVE-2016-2781,LOW,will_not_fix,,bookworm,open,,ignored,low,https://security-tracker.debian.org/tracker/CVE-2016-2781 +baseline,coreutils,9.1-1,coreutils,9.1,CVE-2017-18018,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2017-18018 +baseline,coreutils,9.1-1,coreutils,9.1,CVE-2025-5278,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-5278 +baseline,coreutils,9.1-1,coreutils,9.1,CVE-2026-56391,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-56391 +baseline,coreutils,9.1-1,coreutils,9.1,CVE-2026-56392,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-56392 +baseline,dash,0.5.12-2,dash,0.5.12,CVE-2026-102473,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-102473 +baseline,dash,0.5.12-2,dash,0.5.12,CVE-2026-102474,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-102474 +baseline,diffutils,1:3.8-4,diffutils,3.8,CVE-2026-53910,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-53910 +baseline,gcc-12-base,12.2.0-14+deb12u1,gcc-12,12.2.0,CVE-2026-102010,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-102010 +baseline,gcc-12-base,12.2.0-14+deb12u1,gcc-12,12.2.0,CVE-2026-95619,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95619 +baseline,gcc-12-base,12.2.0-14+deb12u1,gcc-12,12.2.0,CVE-2022-27943,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-27943 +baseline,gpgv,2.2.40-1.1+deb12u2,gnupg2,2.2.40,CVE-2025-30258,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-30258 +baseline,gpgv,2.2.40-1.1+deb12u2,gnupg2,2.2.40,CVE-2025-68972,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-68972 +baseline,gpgv,2.2.40-1.1+deb12u2,gnupg2,2.2.40,CVE-2022-3219,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-3219 +baseline,gpgv,2.2.40-1.1+deb12u2,gnupg2,2.2.40,CVE-2026-105712,LOW,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-105712 +baseline,gpgv,2.2.40-1.1+deb12u2,gnupg2,2.2.40,CVE-2026-57062,LOW,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-57062 +baseline,gzip,1.12-1,gzip,1.12,CVE-2026-41992,HIGH,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-41992 +baseline,gzip,1.12-1,gzip,1.12,CVE-2026-41991,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-41991 +baseline,libacl1,2.3.1-3,acl,2.3.1,CVE-2026-54369,HIGH,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54369 +baseline,libacl1,2.3.1-3,acl,2.3.1,CVE-2026-54370,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54370 +baseline,libapt-pkg6.0,2.6.1,apt,2.6.1,CVE-2011-3374,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2011-3374 +baseline,libattr1,1:2.5.1-4,attr,2.5.1,CVE-2026-54371,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54371 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53613,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53613 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53615,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53615 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-76642,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78408,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78409,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78410,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-13595,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-13595 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-27456,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27456 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-3184,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2022-0563,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +baseline,libblkid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2025-14104,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-14104 +baseline,libbz2-1.0,1.0.8-5+b1,bzip2,1.0.8,CVE-2026-42250,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-42250 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-18374,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-18374 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-19499,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-19499 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-19542,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-19542 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-5435,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5435 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-5450,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5450 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-5928,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5928 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-6238,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6238 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-6368,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6368 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-6791,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6791 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-77117,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-77117 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-80489,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-80489 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-8674,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-8674 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-86805,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-86805 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-89092,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-89092 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-95818,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95818 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2010-4756,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2010-4756 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2018-20796,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2018-20796 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2019-1010022,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010022 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2019-1010023,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010023 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2019-1010024,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010024 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2019-1010025,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010025 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2019-9192,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-9192 +baseline,libc-bin,2.36-9+deb12u14,glibc,2.36,CVE-2026-97399,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-97399 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-18374,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-18374 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-19499,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-19499 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-19542,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-19542 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-5435,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5435 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-5450,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5450 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-5928,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5928 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-6238,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6238 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-6368,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6368 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-6791,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6791 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-77117,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-77117 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-80489,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-80489 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-8674,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-8674 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-86805,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-86805 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-89092,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-89092 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-95818,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95818 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2010-4756,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2010-4756 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2018-20796,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2018-20796 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2019-1010022,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010022 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2019-1010023,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010023 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2019-1010024,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010024 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2019-1010025,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010025 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2019-9192,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-9192 +baseline,libc6,2.36-9+deb12u14,glibc,2.36,CVE-2026-97399,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-97399 +baseline,libgcc-s1,12.2.0-14+deb12u1,gcc-12,12.2.0,CVE-2026-102010,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-102010 +baseline,libgcc-s1,12.2.0-14+deb12u1,gcc-12,12.2.0,CVE-2026-95619,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95619 +baseline,libgcc-s1,12.2.0-14+deb12u1,gcc-12,12.2.0,CVE-2022-27943,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-27943 +baseline,libgcrypt20,1.10.1-3+deb12u1,libgcrypt20,1.10.1,CVE-2018-6829,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2018-6829 +baseline,libgcrypt20,1.10.1-3+deb12u1,libgcrypt20,1.10.1,CVE-2024-2236,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2024-2236 +baseline,libgnutls30,3.7.9-2+deb12u7,gnutls28,3.7.9,CVE-2011-3389,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2011-3389 +baseline,libgnutls30,3.7.9-2+deb12u7,gnutls28,3.7.9,CVE-2026-67693,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-67693 +baseline,libgnutls30,3.7.9-2+deb12u7,gnutls28,3.7.9,CVE-2026-88647,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-88647 +baseline,libgnutls30,3.7.9-2+deb12u7,gnutls28,3.7.9,CVE-2026-88648,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-88648 +baseline,libgnutls30,3.7.9-2+deb12u7,gnutls28,3.7.9,CVE-2026-95184,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-95184 +baseline,libgnutls30,3.7.9-2+deb12u7,gnutls28,3.7.9,CVE-2026-95209,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-95209 +baseline,libgnutls30,3.7.9-2+deb12u7,gnutls28,3.7.9,CVE-2026-95210,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-95210 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53613,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53613 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53615,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53615 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-76642,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78408,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78409,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78410,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-13595,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-13595 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-27456,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27456 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-3184,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2022-0563,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +baseline,libmount1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2025-14104,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-14104 +baseline,libp11-kit0,0.24.1-2,p11-kit,0.24.1,CVE-2026-13757,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-13757 +baseline,libp11-kit0,0.24.1-2,p11-kit,0.24.1,CVE-2026-18938,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-18938 +baseline,libpam-modules,1.5.2-6+deb12u2,pam,1.5.2,CVE-2024-10041,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2024-10041 +baseline,libpam-modules,1.5.2-6+deb12u2,pam,1.5.2,CVE-2026-54411,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54411 +baseline,libpam-modules-bin,1.5.2-6+deb12u2,pam,1.5.2,CVE-2024-10041,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2024-10041 +baseline,libpam-modules-bin,1.5.2-6+deb12u2,pam,1.5.2,CVE-2026-54411,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54411 +baseline,libpam-runtime,1.5.2-6+deb12u2,pam,1.5.2,CVE-2024-10041,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2024-10041 +baseline,libpam-runtime,1.5.2-6+deb12u2,pam,1.5.2,CVE-2026-54411,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54411 +baseline,libpam0g,1.5.2-6+deb12u2,pam,1.5.2,CVE-2024-10041,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2024-10041 +baseline,libpam0g,1.5.2-6+deb12u2,pam,1.5.2,CVE-2026-54411,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54411 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53613,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53613 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53615,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53615 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-76642,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78408,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78409,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78410,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-13595,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-13595 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-27456,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27456 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-3184,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2022-0563,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +baseline,libsmartcols1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2025-14104,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-14104 +baseline,libstdc++6,12.2.0-14+deb12u1,gcc-12,12.2.0,CVE-2026-102010,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-102010 +baseline,libstdc++6,12.2.0-14+deb12u1,gcc-12,12.2.0,CVE-2026-95619,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95619 +baseline,libstdc++6,12.2.0-14+deb12u1,gcc-12,12.2.0,CVE-2022-27943,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-27943 +baseline,libsystemd0,252.39-1~deb12u2,systemd,252.39,CVE-2026-16742,HIGH,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-16742 +baseline,libsystemd0,252.39-1~deb12u2,systemd,252.39,CVE-2026-15059,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-15059 +baseline,libsystemd0,252.39-1~deb12u2,systemd,252.39,CVE-2013-4392,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2013-4392 +baseline,libsystemd0,252.39-1~deb12u2,systemd,252.39,CVE-2023-31437,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31437 +baseline,libsystemd0,252.39-1~deb12u2,systemd,252.39,CVE-2023-31438,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31438 +baseline,libsystemd0,252.39-1~deb12u2,systemd,252.39,CVE-2023-31439,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31439 +baseline,libsystemd0,252.39-1~deb12u2,systemd,252.39,CVE-2026-40228,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-40228 +baseline,libtasn1-6,4.19.0-2+deb12u1,libtasn1-6,4.19.0,CVE-2025-13151,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-13151 +baseline,libtinfo6,6.4-4,ncurses,6.4,CVE-2025-69720,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-69720 +baseline,libtinfo6,6.4-4,ncurses,6.4,CVE-2023-50495,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2023-50495 +baseline,libtinfo6,6.4-4,ncurses,6.4,CVE-2025-6141,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-6141 +baseline,libudev1,252.39-1~deb12u2,systemd,252.39,CVE-2026-16742,HIGH,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-16742 +baseline,libudev1,252.39-1~deb12u2,systemd,252.39,CVE-2026-15059,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-15059 +baseline,libudev1,252.39-1~deb12u2,systemd,252.39,CVE-2013-4392,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2013-4392 +baseline,libudev1,252.39-1~deb12u2,systemd,252.39,CVE-2023-31437,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31437 +baseline,libudev1,252.39-1~deb12u2,systemd,252.39,CVE-2023-31438,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31438 +baseline,libudev1,252.39-1~deb12u2,systemd,252.39,CVE-2023-31439,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31439 +baseline,libudev1,252.39-1~deb12u2,systemd,252.39,CVE-2026-40228,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-40228 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53613,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53613 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53615,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53615 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-76642,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78408,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78409,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78410,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-13595,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-13595 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-27456,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27456 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-3184,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2022-0563,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +baseline,libuuid1,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2025-14104,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-14104 +baseline,login,1:4.13+dfsg1-1+deb12u2,shadow,4.13+dfsg1,CVE-2007-5686,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2007-5686 +baseline,login,1:4.13+dfsg1-1+deb12u2,shadow,4.13+dfsg1,CVE-2024-56433,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2024-56433 +baseline,login,1:4.13+dfsg1-1+deb12u2,shadow,4.13+dfsg1,TEMP-0628843-DBAD28,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0628843-DBAD28 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53613,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53613 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53615,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53615 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-76642,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78408,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78409,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78410,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-13595,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-13595 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-27456,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27456 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-3184,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2022-0563,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +baseline,mount,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2025-14104,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-14104 +baseline,ncurses-base,6.4-4,ncurses,6.4,CVE-2025-69720,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-69720 +baseline,ncurses-base,6.4-4,ncurses,6.4,CVE-2023-50495,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2023-50495 +baseline,ncurses-base,6.4-4,ncurses,6.4,CVE-2025-6141,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-6141 +baseline,ncurses-bin,6.4-4,ncurses,6.4,CVE-2025-69720,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-69720 +baseline,ncurses-bin,6.4-4,ncurses,6.4,CVE-2023-50495,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2023-50495 +baseline,ncurses-bin,6.4-4,ncurses,6.4,CVE-2025-6141,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-6141 +baseline,passwd,1:4.13+dfsg1-1+deb12u2,shadow,4.13+dfsg1,CVE-2007-5686,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2007-5686 +baseline,passwd,1:4.13+dfsg1-1+deb12u2,shadow,4.13+dfsg1,CVE-2024-56433,LOW,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2024-56433 +baseline,passwd,1:4.13+dfsg1-1+deb12u2,shadow,4.13+dfsg1,TEMP-0628843-DBAD28,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0628843-DBAD28 +baseline,perl-base,5.36.0-7+deb12u4,perl,5.36.0,CVE-2026-9538,HIGH,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-9538 +baseline,perl-base,5.36.0-7+deb12u4,perl,5.36.0,CVE-2026-15534,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-15534 +baseline,perl-base,5.36.0-7+deb12u4,perl,5.36.0,CVE-2011-4116,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2011-4116 +baseline,perl-base,5.36.0-7+deb12u4,perl,5.36.0,CVE-2023-31486,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31486 +baseline,perl-base,5.36.0-7+deb12u4,perl,5.36.0,CVE-2026-82560,UNKNOWN,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-82560 +baseline,sysvinit-utils,3.06-4,sysvinit,3.06,TEMP-0517018-A83CE6,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0517018-A83CE6 +baseline,tar,1.34+dfsg-1.2+deb12u1,tar,1.34+dfsg,CVE-2026-18477,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-18477 +baseline,tar,1.34+dfsg-1.2+deb12u1,tar,1.34+dfsg,CVE-2026-18508,MEDIUM,fix_deferred,,bookworm,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-18508 +baseline,tar,1.34+dfsg-1.2+deb12u1,tar,1.34+dfsg,CVE-2026-5704,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5704 +baseline,tar,1.34+dfsg-1.2+deb12u1,tar,1.34+dfsg,CVE-2005-2541,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2005-2541 +baseline,tar,1.34+dfsg-1.2+deb12u1,tar,1.34+dfsg,TEMP-0290435-0B57B5,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0290435-0B57B5 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53613,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53613 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53615,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53615 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-76642,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78408,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78409,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78410,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-13595,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-13595 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-27456,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27456 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-3184,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2022-0563,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +baseline,util-linux,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2025-14104,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-14104 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53613,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53613 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-53615,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-53615 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-76642,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78408,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78409,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-78410,HIGH,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-13595,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-13595 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-27456,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27456 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2026-3184,MEDIUM,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2022-0563,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +baseline,util-linux-extra,2.38.1-5+deb12u3,util-linux,2.38.1,CVE-2025-14104,LOW,affected,,bookworm,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-14104 +baseline,zlib1g,1:1.2.13.dfsg-1,zlib,1.2.13.dfsg,CVE-2023-45853,CRITICAL,will_not_fix,,bookworm,open,,ignored,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2023-45853 +baseline,zlib1g,1:1.2.13.dfsg-1,zlib,1.2.13.dfsg,CVE-2026-27171,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27171 +baseline,zlib1g,1:1.2.13.dfsg-1,zlib,1.2.13.dfsg,CVE-2026-85091,MEDIUM,affected,,bookworm,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-85091 +candidate,apt,3.0.3,apt,3.0.3,CVE-2011-3374,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2011-3374 +candidate,bash,5.2.37-2+b10,bash,5.2.37,TEMP-0841856-B18BAF,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0841856-B18BAF +candidate,bsdutils,1:2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-76642,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +candidate,bsdutils,1:2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78408,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +candidate,bsdutils,1:2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78409,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +candidate,bsdutils,1:2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78410,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +candidate,bsdutils,1:2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-3184,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +candidate,bsdutils,1:2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2022-0563,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +candidate,coreutils,9.7-3,coreutils,9.7,CVE-2017-18018,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2017-18018 +candidate,coreutils,9.7-3,coreutils,9.7,CVE-2025-5278,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-5278 +candidate,coreutils,9.7-3,coreutils,9.7,CVE-2026-56391,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-56391 +candidate,coreutils,9.7-3,coreutils,9.7,CVE-2026-56392,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-56392 +candidate,dash,0.5.12-12,dash,0.5.12,CVE-2026-102473,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-102473 +candidate,dash,0.5.12-12,dash,0.5.12,CVE-2026-102474,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-102474 +candidate,diffutils,1:3.10-4,diffutils,3.10,CVE-2026-53910,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2026-53910 +candidate,gcc-14-base,14.2.0-19,gcc-14,14.2.0,CVE-2026-102010,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-102010 +candidate,gcc-14-base,14.2.0-19,gcc-14,14.2.0,CVE-2026-95619,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95619 +candidate,libacl1,2.3.2-2+b1,acl,2.3.2,CVE-2026-54369,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54369 +candidate,libacl1,2.3.2-2+b1,acl,2.3.2,CVE-2026-54370,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54370 +candidate,libapt-pkg7.0,3.0.3,apt,3.0.3,CVE-2011-3374,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2011-3374 +candidate,libattr1,1:2.5.2-3,attr,2.5.2,CVE-2026-54371,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54371 +candidate,libblkid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-76642,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +candidate,libblkid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78408,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +candidate,libblkid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78409,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +candidate,libblkid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78410,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +candidate,libblkid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-3184,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +candidate,libblkid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2022-0563,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +candidate,libbz2-1.0,1.0.8-6,bzip2,1.0.8,CVE-2026-42250,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-42250 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-18374,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-18374 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-19499,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-19499 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-19542,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-19542 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-5435,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5435 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-6238,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6238 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-6368,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6368 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-6791,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6791 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-77117,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-77117 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-80489,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-80489 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-8674,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-8674 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-86805,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-86805 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-89092,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-89092 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-95818,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95818 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2010-4756,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2010-4756 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2018-20796,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2018-20796 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2019-1010022,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010022 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2019-1010023,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010023 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2019-1010024,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010024 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2019-1010025,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010025 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2019-9192,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-9192 +candidate,libc-bin,2.41-12+deb13u4,glibc,2.41,CVE-2026-97399,LOW,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-97399 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-18374,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-18374 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-19499,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-19499 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-19542,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-19542 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-5435,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5435 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-6238,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6238 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-6368,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6368 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-6791,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-6791 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-77117,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-77117 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-80489,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-80489 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-8674,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-8674 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-86805,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-86805 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-89092,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-89092 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-95818,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95818 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2010-4756,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2010-4756 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2018-20796,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2018-20796 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2019-1010022,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010022 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2019-1010023,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010023 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2019-1010024,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010024 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2019-1010025,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-1010025 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2019-9192,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2019-9192 +candidate,libc6,2.41-12+deb13u4,glibc,2.41,CVE-2026-97399,LOW,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-97399 +candidate,libgcc-s1,14.2.0-19,gcc-14,14.2.0,CVE-2026-102010,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-102010 +candidate,libgcc-s1,14.2.0-19,gcc-14,14.2.0,CVE-2026-95619,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95619 +candidate,liblastlog2-2,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-76642,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +candidate,liblastlog2-2,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78408,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +candidate,liblastlog2-2,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78409,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +candidate,liblastlog2-2,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78410,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +candidate,liblastlog2-2,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-3184,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +candidate,liblastlog2-2,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2022-0563,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +candidate,libmount1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-76642,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +candidate,libmount1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78408,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +candidate,libmount1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78409,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +candidate,libmount1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78410,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +candidate,libmount1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-3184,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +candidate,libmount1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2022-0563,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +candidate,libpam-modules,1.7.0-5,pam,1.7.0,CVE-2026-54411,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54411 +candidate,libpam-modules-bin,1.7.0-5,pam,1.7.0,CVE-2026-54411,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54411 +candidate,libpam-runtime,1.7.0-5,pam,1.7.0,CVE-2026-54411,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54411 +candidate,libpam0g,1.7.0-5,pam,1.7.0,CVE-2026-54411,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-54411 +candidate,libsmartcols1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-76642,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +candidate,libsmartcols1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78408,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +candidate,libsmartcols1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78409,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +candidate,libsmartcols1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78410,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +candidate,libsmartcols1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-3184,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +candidate,libsmartcols1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2022-0563,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +candidate,libsqlite3-0,3.46.1-7+deb13u2,sqlite3,3.46.1,CVE-2026-50812,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-50812 +candidate,libsqlite3-0,3.46.1-7+deb13u2,sqlite3,3.46.1,CVE-2026-50813,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-50813 +candidate,libsqlite3-0,3.46.1-7+deb13u2,sqlite3,3.46.1,CVE-2021-45346,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2021-45346 +candidate,libsqlite3-0,3.46.1-7+deb13u2,sqlite3,3.46.1,CVE-2025-70873,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2025-70873 +candidate,libstdc++6,14.2.0-19,gcc-14,14.2.0,CVE-2026-102010,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-102010 +candidate,libstdc++6,14.2.0-19,gcc-14,14.2.0,CVE-2026-95619,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-95619 +candidate,libsystemd0,257.13-1~deb13u1,systemd,257.13,CVE-2026-16742,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-16742 +candidate,libsystemd0,257.13-1~deb13u1,systemd,257.13,CVE-2026-15059,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-15059 +candidate,libsystemd0,257.13-1~deb13u1,systemd,257.13,CVE-2013-4392,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2013-4392 +candidate,libsystemd0,257.13-1~deb13u1,systemd,257.13,CVE-2023-31437,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31437 +candidate,libsystemd0,257.13-1~deb13u1,systemd,257.13,CVE-2023-31438,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31438 +candidate,libsystemd0,257.13-1~deb13u1,systemd,257.13,CVE-2023-31439,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31439 +candidate,libsystemd0,257.13-1~deb13u1,systemd,257.13,CVE-2026-40228,LOW,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-40228 +candidate,libtinfo6,6.5+20250216-2,ncurses,6.5+20250216,CVE-2025-69720,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-69720 +candidate,libtinfo6,6.5+20250216-2,ncurses,6.5+20250216,CVE-2025-6141,LOW,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-6141 +candidate,libudev1,257.13-1~deb13u1,systemd,257.13,CVE-2026-16742,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-16742 +candidate,libudev1,257.13-1~deb13u1,systemd,257.13,CVE-2026-15059,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-15059 +candidate,libudev1,257.13-1~deb13u1,systemd,257.13,CVE-2013-4392,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2013-4392 +candidate,libudev1,257.13-1~deb13u1,systemd,257.13,CVE-2023-31437,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31437 +candidate,libudev1,257.13-1~deb13u1,systemd,257.13,CVE-2023-31438,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31438 +candidate,libudev1,257.13-1~deb13u1,systemd,257.13,CVE-2023-31439,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2023-31439 +candidate,libudev1,257.13-1~deb13u1,systemd,257.13,CVE-2026-40228,LOW,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-40228 +candidate,libuuid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-76642,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +candidate,libuuid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78408,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +candidate,libuuid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78409,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +candidate,libuuid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78410,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +candidate,libuuid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-3184,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +candidate,libuuid1,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2022-0563,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +candidate,login,1:4.16.0-2+really2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-76642,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +candidate,login,1:4.16.0-2+really2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78408,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +candidate,login,1:4.16.0-2+really2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78409,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +candidate,login,1:4.16.0-2+really2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78410,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +candidate,login,1:4.16.0-2+really2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-3184,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +candidate,login,1:4.16.0-2+really2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2022-0563,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +candidate,login.defs,1:4.17.4-2,shadow,4.17.4,CVE-2007-5686,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2007-5686 +candidate,login.defs,1:4.17.4-2,shadow,4.17.4,CVE-2024-56433,LOW,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2024-56433 +candidate,login.defs,1:4.17.4-2,shadow,4.17.4,TEMP-0628843-DBAD28,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0628843-DBAD28 +candidate,mount,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-76642,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +candidate,mount,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78408,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +candidate,mount,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78409,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +candidate,mount,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78410,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +candidate,mount,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-3184,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +candidate,mount,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2022-0563,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +candidate,ncurses-base,6.5+20250216-2,ncurses,6.5+20250216,CVE-2025-69720,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-69720 +candidate,ncurses-base,6.5+20250216-2,ncurses,6.5+20250216,CVE-2025-6141,LOW,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-6141 +candidate,ncurses-bin,6.5+20250216-2,ncurses,6.5+20250216,CVE-2025-69720,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-69720 +candidate,ncurses-bin,6.5+20250216-2,ncurses,6.5+20250216,CVE-2025-6141,LOW,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2025-6141 +candidate,passwd,1:4.17.4-2,shadow,4.17.4,CVE-2007-5686,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2007-5686 +candidate,passwd,1:4.17.4-2,shadow,4.17.4,CVE-2024-56433,LOW,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2024-56433 +candidate,passwd,1:4.17.4-2,shadow,4.17.4,TEMP-0628843-DBAD28,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0628843-DBAD28 +candidate,perl-base,5.40.1-6+deb13u1,perl,5.40.1,CVE-2026-9538,HIGH,fix_deferred,,trixie,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-9538 +candidate,perl-base,5.40.1-6+deb13u1,perl,5.40.1,CVE-2026-15534,MEDIUM,fix_deferred,,trixie,open,,postponed,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-15534 +candidate,perl-base,5.40.1-6+deb13u1,perl,5.40.1,CVE-2011-4116,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2011-4116 +candidate,perl-base,5.40.1-6+deb13u1,perl,5.40.1,CVE-2026-82560,UNKNOWN,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-82560 +candidate,sysvinit-utils,3.14-4,sysvinit,3.14,TEMP-0517018-A83CE6,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0517018-A83CE6 +candidate,tar,1.35+dfsg-3.1,tar,1.35+dfsg,CVE-2026-18477,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-18477 +candidate,tar,1.35+dfsg-3.1,tar,1.35+dfsg,CVE-2026-18508,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-18508 +candidate,tar,1.35+dfsg-3.1,tar,1.35+dfsg,CVE-2026-5704,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-5704 +candidate,tar,1.35+dfsg-3.1,tar,1.35+dfsg,CVE-2005-2541,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2005-2541 +candidate,tar,1.35+dfsg-3.1,tar,1.35+dfsg,TEMP-0290435-0B57B5,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/TEMP-0290435-0B57B5 +candidate,util-linux,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-76642,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-76642 +candidate,util-linux,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78408,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78408 +candidate,util-linux,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78409,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78409 +candidate,util-linux,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-78410,HIGH,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-78410 +candidate,util-linux,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2026-3184,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-3184 +candidate,util-linux,2.41.5-0+deb13u1,util-linux,2.41.5,CVE-2022-0563,LOW,affected,,trixie,open,,,unimportant,https://security-tracker.debian.org/tracker/CVE-2022-0563 +candidate,zlib1g,1:1.3.dfsg+really1.3.1-1+b1,zlib,1.3.dfsg+really1.3.1,CVE-2026-27171,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-27171 +candidate,zlib1g,1:1.3.dfsg+really1.3.1-1+b1,zlib,1.3.dfsg+really1.3.1,CVE-2026-85091,MEDIUM,affected,,trixie,open,,,not yet assigned,https://security-tracker.debian.org/tracker/CVE-2026-85091 diff --git a/docs/quality/SELF_HOST_OS_TRIAGE_2026-10-28.md b/docs/quality/SELF_HOST_OS_TRIAGE_2026-10-28.md new file mode 100644 index 00000000..24f6ffe1 --- /dev/null +++ b/docs/quality/SELF_HOST_OS_TRIAGE_2026-10-28.md @@ -0,0 +1,103 @@ +# Self-host base maintenance and complete Debian advisory inventory + +This #209 candidate starts from integrated dev `780a7599d1d7949d5e0fac56091f0b07bb0e6e0d`. +It updates the self-host API’s three Node stages from Bookworm to a pinned +Node 22/Trixie slim manifest and applies available runtime package upgrades. +The application remains on glibc; UID 1000, its auth-data path, compiled native +SQLite, API/MCP entry points and production secret requirements are preserved. +It publishes or deploys nothing. + +## Selection and residual risk + +[Node’s image documentation](https://github.com/nodejs/docker-node/blob/main/README.md#supported-architectures) +identifies Alpine/musl support as experimental. The existing Alpine API image +has a smaller scan inventory, but moving self-host to it would add a libc and +platform-support tradeoff. This change instead evaluates the maintained Debian 13 +line. A lower scanner count alone does not establish runtime safety. + +| Inventory, 2026-10-28 UTC | Bookworm baseline | Trixie candidate | +| --- | ---: | ---: | +| OS packages | 88 | 79 | +| OS package/advisory entries | 228 | 162 | +| Critical / high / medium / low / unknown | 1 / 56 / 87 / 83 / 1 | 0 / 43 / 58 / 60 / 1 | +| Affected / deferred / will-not-fix scanner status | 170 / 41 / 17 | 160 / 2 / 0 | +| Unique source-package/advisory pairs | 99 | 71 | +| npm packages / findings | 377 / 0 | 377 / 0 | + +The [complete CSV](SELF_HOST_OS_TRIAGE_2026-10-28.csv) retains **all 228 baseline +and 162 candidate entries**, its installed binary/source version, scanner status, +Debian release status/fixed version/deferral reason and direct vendor advisory +link. All 390 entries resolve in the dated primary tracker snapshot. No rows are +suppressed or converted to a zero-finding claim. Neither scan reports an +available fixed package version for its remaining findings. Trixie’s residual +vendor states are 160 open entries and 2 postponed entries; they remain unresolved. + +Compared by source package and advisory, 34 baseline pairs disappear and 6 new +pairs appear. Four new pairs concern the OS `sqlite3` package: CVE-2026-50812, +CVE-2026-50813, CVE-2021-45346 and CVE-2025-70873. Its installed binary is a +`liblastlog2-2` dependency. The tested Node and application SQLite native addon +linkage does not include system `libsqlite3`; that observation does not establish +unreachability through every installed program. The other two concern `gcc-14`: +CVE-2026-102010 and CVE-2026-95619. Node and the native addon do link libstdc++; +these remain explicit applicability risks, without an untested exemption. + +The baseline critical entry CVE-2023-45853 needs its specific +[Debian assessment](https://security-tracker.debian.org/tracker/CVE-2023-45853): +Bookworm does not build the affected contrib/minizip component. That assessment +cannot be generalized to other advisories. The candidate no longer reports that +entry. This is a maintenance improvement with residual OS findings, not full +image-security clearance or authorization to release. + +## Exact local provenance + +- Node base index: `node:22-trixie-slim@sha256:8eb619e402eaede1748a3c8173bffab391592812e85136452f781741c7242ddf`. + Its linux/amd64 manifest is `sha256:93c4ef18bd9f668c013e8800677f6709fe426db3f29fe758d34dd72600018a7e`. + The observed runtime is Node 22.23.4 on Debian 13.7. +- Candidate local image reference: `sha256:21058831b3b9af15ac6f8f9f7246b96fd97313a7e19b3d6218214c644b2a37f4`; + scanned config ID: `sha256:348f5dfe78123f6fc06cb4f1eaabd4e8617eb5f9c29b47e989fffced61c9269a`. +- Baseline local image reference: `sha256:8d464a8965e84b815e0c5172ccac982b9942a50b07a474e5dac83ce72c637ea4`; + scanned config ID: `sha256:b6f3f2712d57e80d4ab65e8ddbfa50a53278bfb3aa8d960745f021bfc0f448da`. + This is the retained #211 packaging image (reviewed source `7d327c1`); Core/server/ + MCP source paths are unchanged between that commit and integrated dev 780a759. + Its complete package/version and advisory inventories also match the final + dev CI self-host scan, whose separate config ID is + `sha256:a4890a142c16079a6fce7dae49de8bc9cbee0ade2a8c03644a44c865d5de8074`. +- Scanner: Trivy 0.76.1, + `aquasec/trivy@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c`. + DB updated 2026-10-27T19:17:44.708952708Z, next update 2026-10-28T19:17:44.708952538Z. +- Raw baseline scan SHA256: `8bc29221f686de884e5c5f27a40d844740c3e0781ab8cb199b0ac5ca31a3de5d`. + Candidate scan SHA256: `cac1783fe2914e5b221b4dff25a36013bff9bd6e7664309f0d48189c151a5720`. +- [Debian primary tracker JSON](https://security-tracker.debian.org/tracker/data/json) + downloaded 2026-10-28; SHA256 + `0e8487c7976df106b5772472d5b50434ac060e9ce8c5e9fc0d1b74cdd6dab1d5`. + +Local raw evidence is retained in `/tmp/graphdone-resolution-20261027` with the +`selfhost-trixie-` prefix, the baseline `runtime-selfhost-tzdata-trivy.json`, and +`debian-security-tracker-20261028.json`. These are preparatory working-tree +results; the containing PR’s exact-head checks and independent review still +govern integration. CI artifacts supply the published final image provenance. +Pinned base refreshes must be reviewed regularly; pinning is not automatic +security maintenance. Apt repositories and scanner data can change, so image, +package and database provenance must accompany later builds too. + +## Runtime and rollback evidence + +The existing runtime contract now optionally accepts an immutable baseline +image ID. It starts baseline → candidate → replacement candidate → baseline +rollback using one owned auth-data volume, checks the same SQLite account ID, +and exercises real CAPTCHA, API, compiled MCP stdio and independent Neo4j +readback on every stage. It verifies fixture deletion and removes its owned +containers and volume. The default two-stage CI mode remains available. + +```sh +GRAPHDONE_DISPOSABLE_NEO4J=1 node tests/runtime/run-image.mjs 1000 +``` + +Explicit loopback Neo4j fixture credentials are also required. All four local +stages passed; the candidate also passed UID 1000, native SQLite, package inventory +and default-secret rejection. The observed 393 installed-package entries in the +contract are a different counting method from Trivy’s 377 npm packages. No +production graph or auth volume was used. Local and CI evidence is linux/amd64; +this is not an arm64 validation claim. Restoring an old image also restores its +known vulnerabilities, so rollback is an operational escape path, not a security +remediation. No application schema or user-data migration is introduced here. diff --git a/docs/quality/TEST_STRATEGY.md b/docs/quality/TEST_STRATEGY.md new file mode 100644 index 00000000..00d5f1d9 --- /dev/null +++ b/docs/quality/TEST_STRATEGY.md @@ -0,0 +1,43 @@ +# Coverage and limits + +No current suite proves that every button, menu, role and failure mode works. Passing a route render is not interaction coverage. A test which skips, catches a failed click, returns when data is absent, or only checks that some nodes render is not a release gate for that action. + +The [2026-10-23 Core merge-validation ledger](CORE_VALIDATION_2026-10-23.md) is the +canonical record for this sweep's source heads, retained failures, final combined +gates and release limits. Its pending fields must be completed with actual +results; scoped PR evidence is not proof of the deployed site or all 62 +[new-user acceptance questions](NEW_USER_GATE.md). + +| Area | Repeatable evidence | Remaining gate | +|---|---|---| +| Entry and responsive dialogs | `guest-dialog-layout`, `graph-details`, `admin-dialog-responsive` | Real account recovery, provider failures, mobile keyboard, native assistive technology | +| All eight workspace modes | `new-user-modes` at three sizes | Every filter, sort, date control, selection and bulk action | +| Task lifecycle | Six `persona-lifecycle` scenarios, saved notes/status/date | Multi-actor assignment, conflicts, retry after interrupted save | +| Graph interactions | `user-smoke`, `graph-details`, `graph-motion`, `graph-resize` | Strict connection menu matrix, labels crossing cards, dense graph interaction budgets | +| Layout | `graph-layout`: 12 stacked, 50 and 200 connected cards | Larger sizes, edge routing and intentional collapsed groups | +| Security | [Bounded Core folder/current-role, queue-owner, magic-link, password-budget and production-startup regressions](CORE_VALIDATION_2026-10-23.md); separate Cloud `test-multiple-graphs`, `test-security-limits` | Complete permission/session matrix; server CAPTCHA, token purpose, OAuth linking, injection/XSS, upload/export, residual dependencies and hosted parity; see [dated security scope](SECURITY_SCOPE_2026-10-23.md) | +| Cost bounds | Local D1 eight concurrent batches at 295/300 nodes | Distributed Worker concurrency, edge/project quotas, rate-limiter binding, long soak | +| Release | Build/typecheck/unit checks and Pages artifact script | Exact deployed artifact, migrations, rollback and live account journey | + +## Control inventory rule + +Each action needs: stable name, route/view, actor permission, initial state, pointer/touch/keyboard input, expected visible result, persistence or rejection assertion, and test reference. Destructive actions use owned disposable fixtures. An enumerated control is DISCOVERED until its result is asserted; a crawler clicking everything does not prove correctness. + +Start with graph-first controls: graph selector/create/switch; new item/create/cancel; node open/close; title/description; every type and status; priority keyboard/touch; note add/edit/delete; details/save/cancel; dates/tags; grow/connect/cancel; relationship type/reverse/delete; undo/redo; zoom/frame/pan; layout and minimap. Then apply the same format to each alternate view, account, settings and administrator page. + +## Stress procedure + +Run one headed browser at a time on localhost with disposable graphs. Increase dataset size and concurrency separately. Record node/edge count, viewport, quality setting, load time, full rendered count, actual successful drag, frame gaps, interaction latency, API p50/p95, settling time, overlap counts and persistence after reload. Stop escalation on sustained unresponsiveness, server errors or failed correctness; retain the failing size. A maximum tested size is not the application limit. Do not run the entire unbounded MCP chaos suite alongside the browser: its previous run exhausted memory. + +Security demand-control checks follow the [OWASP GraphQL testing guidance](https://github.com/OWASP/www-project-web-security-testing-guide/blob/master/v42/4-Web_Application_Security_Testing/12-API_Testing/01-Testing_GraphQL.md). They complement authorization, payload, request-rate and data-volume limits; no single depth check establishes security. + +## Runtime and evidence incidents + +- [2026-10-23 production secret startup](PRODUCTION_SECRETS_2026-10-23.md): missing/default JWT or session secrets, environment loading after signing-key capture, and direct API containers lacking production mode. +- [2026-10-23 delayed details-save completion](DETAILS_SAVE_COMPLETION_2026-10-23.md): an acknowledged mutation's pending refetch erased a newer due-date edit; a delayed completion also repopulated cleared undo history after graph changes. Controlled Apollo component tests retain both the failing baseline and correction. + +- [2026-10-23 runtime validation](RUNTIME_VALIDATION_2026-10-23.md): API peer-resolution failure after a successful build, obsolete Node/container minimums, a missing disposable-DB CI marker, and smoke assertions depending on a populated default graph. + +- 2026-10-23 mobile login evidence: a headed 360px admin scenario successfully signed in but the shared helper checked only the hidden desktop selector/user menu. It retried login on the authenticated workspace and ultimately reported a missing password field. Include the existing visible `graph-selector-mobile` marker in the positive workspace indicators; retain the login-form exclusion. Reproduce with `tests/e2e/graph/admin-dialog-responsive.spec.ts` at 360, 844, and 1440px. This helper observes workspace entry; it is not a complete session/identity authorization test. The initial failure screenshot and log are retained in the audit artifacts. + +- The same admin scenario also assumed the Users panel was the default; the current default is Feedback & operations. Navigate through the visible Users button before opening Create User. Keep the dialog visibility, focus, viewport, dismissal, and focus-restoration assertions intact. diff --git a/docs/quality/TEST_TIMEOUT_REVIEW_2026-10-23.md b/docs/quality/TEST_TIMEOUT_REVIEW_2026-10-23.md new file mode 100644 index 00000000..d206b93a --- /dev/null +++ b/docs/quality/TEST_TIMEOUT_REVIEW_2026-10-23.md @@ -0,0 +1,65 @@ +# A wrapper timeout must also stop its owned test workers + +Date: 2026-10-23. Tracking: [issue #162](https://github.com/GraphDone/GraphDone-Core/issues/162). + +## Evidence and cause + +The previous `runCommand` killed only its immediate child and resolved on that +child's `close` event. An `npx` wrapper can have test-runner descendants that +inherit its output pipes. Killing the wrapper does not stop those descendants, +and the open pipes keep `close` from firing. + +A bounded local Node probe configured `timeoutMs: 100`, started a descendant +that wrote after 1,000 ms, and returned this result on 2026-10-23: + +```json +{"code":null,"stdout":"descendant still running after parent timeout\n","durationMs":1069,"timedOut":true} +``` + +Before the fix, the focused regression command was: + +```bash +node --test tests/lib/runner/runSequence.test.mjs +``` + +The ordinary-descendant and pre-exited-wrapper cases each took approximately +4,060 ms despite a 600 ms timeout. A descendant that closed its output and +ignored SIGTERM remained running after the promise resolved. An escaped-group +fixture also held output pipes open past the deadline. + +## Corrective rule + +On POSIX, spawn each command in its own process group and address only that +group. On timeout, send SIGTERM, wait the bounded grace period, then send +SIGKILL. Complete forced cleanup even if the wrapper closed during the grace +period. Bound output draining separately so an escaped descendant cannot hold +the caller forever. Always return `timedOut: true` after a deadline, including +when the wrapper exited with code zero. + +Detaching a command also separates it from the terminal's process group. The +runner therefore shares one SIGINT/SIGTERM cleanup handler across all active +commands, immediately forces cleanup of their owned groups, and removes its +handlers when the last command settles. Cleanup is prepended ahead of existing +caller-owned handlers, including handlers that exit immediately; those handlers +remain in control of exit behavior. Otherwise cancellation exits with 130/143. + +## Regression coverage and limits + +The focused file uses real local Node processes and temporary files. It checks +ordinary descendants, ignored SIGTERM, closed pipes, already-exited wrappers, +unrelated-process survival, bounded escaped-group output, simultaneous-command +interrupts, listener removal, and preservation of caller signal handling. +Success output, working directory, environment, literal argv, and spawn errors +remain covered. The existing `npm run test:unified:lib` discovers these tests. + +On 2026-10-23, `node --test tests/lib/` passed all 117 tests, including the 12 +new process-runner cases; `git diff --check` passed. The failing-before cases +establish that the new checks detect the previous timeout behavior. + +Linux validates group cleanup. POSIX-specific tests skip Windows, where the +runner only terminates the direct child and bounds its pipe wait. Deliberately +detached descendants remain outside the original group and are not a supported +containment boundary. The escaped-group fixture is explicitly cleaned up by +its test. SIGKILL of the runner, OS stalls, and a blocked Node event loop cannot +be repaired by a JavaScript signal handler. Production services, application +data, and browser acceptance are outside this regression's scope. diff --git a/docs/quality/TOOLCHAIN_MIGRATION_2026-10-27.md b/docs/quality/TOOLCHAIN_MIGRATION_2026-10-27.md new file mode 100644 index 00000000..748514ec --- /dev/null +++ b/docs/quality/TOOLCHAIN_MIGRATION_2026-10-27.md @@ -0,0 +1,119 @@ +# Patched build and test toolchain — 2026-10-27 + +This is the compatible-toolchain slice of #185. Runtime-image packaging and +Turbo/Mermaid updates remain separate. This evidence does not establish image, +Windows, hosted-backend or deployment clearance. + +## Versions and compatibility + +| Consumer | Previous lock | Reviewed line | +| --- | --- | --- | +| Vite, declared in all four tested packages | 5.4.21 | 7.3.7 | +| React transform | 4.7.0 | 5.2.0 | +| Vitest and matching V8 coverage | 1.6.1 | 4.1.12 | +| Worker implementation | tinypool 0.8.4 | Vitest 4 native pool; tinypool removed | + +Node 22.18 and 24 satisfy the installed engine/peer contracts. Vite 7 and +Vitest 4 are patched compatible lines; this change does not require adopting +Vite 8's different bundler. All locked and actually resolved copies are guarded +by `tests/lib/dependencies/toolchain.test.mjs`. The guard fails ten cases against +the old lock/install, with one compatibility control still passing. + +Authoritative references: + +- [Vite 7 migration](https://v7.vite.dev/guide/migration): explicit build targets + retain ES2020, Chrome 87, Edge 88, Firefox 78 and Safari 14. +- [Vite non-Windows file boundary advisory](https://github.com/advisories/GHSA-rq7h-c2jc-7f22): + 7.3.7 is patched; network exposure and path conditions matter. +- [Vite Windows alternate-path advisory](https://github.com/advisories/GHSA-fx2h-pf6j-xcff): + the selected line also contains its fix; this is not a Windows execution test. +- [Vitest UI advisory](https://github.com/advisories/GHSA-5xrq-8626-4rwp): + 4.1.0 and later on this line are patched; ordinary run mode does not prove UI exposure. +- [Vitest 4 migration](https://v4.vitest.dev/guide/migration): worker pool, + test discovery and coverage behavior require explicit migration. +- [Tinypool advisory](https://github.com/advisories/GHSA-85c8-ppgw-ccpr): + removing its actual dependency paths avoids replacing a worker major under Vitest 1. + +## Preserved contracts + +- Explicit `**/dist/**` exclusion prevents compiled server tests being counted + twice. Exact discovered files match tracked sources both with and without + compiled outputs: core 3, server 16, MCP 11, web 45. +- Coverage explicitly includes every package's source plus executable + `packages/web/offline-shell.ts`. Unimported modules remain zero-hit entries; + tests and compiled output are excluded. V8's new mapping changes percentages, + so compare scope and exercised behavior rather than equating old percentages. +- MCP remains isolated forks, one worker, no parallel test files. Web/server + use two workers; existing test deadlines and retry policy are unchanged. +- DOM matcher setup uses the Vitest entry point. Lock deduplication makes its + augmentation resolve the same installed runner; unrelated ESLint patches and + their placements were restored. +- The editor lifecycle fixture queries the controls' explicit accessible labels + and still asserts native button semantics, all writes, pending refetches, + drafts and undo snapshots. It avoids repeatedly computing roles across the + entire dialog during the same five-second test. +- The HTTP CAPTCHA fixture searches the actual signed SHA-256 challenge using + native hashing with the unchanged 100,000 bound. Server verification, action + binding, replay rejection and the 20-second deadline remain unchanged. Real + browser acceptance still uses ALTCHA's browser solver. + +## Validation and retained failures + +Pinned clean install, build, types and lint passed. Current Node 22 coverage +runs pass core 41, server 211, web 353 and MCP 110; the 26 explicitly opted-in +MCP database cases are not counted as passes in ordinary unit mode. The actual +owned Neo4j run passed all 136 MCP cases with the existing CI CPU-monitor mode. + +Node 24 core/server/MCP coverage passed; current web coverage passed all 353 +cases after correcting the fixture costs below. Final branch CI runs coverage +for both supported Node versions and remains required before integration. + +The current harness passes 192/192. The real unified web adapter passes 353 +with no skips or warnings. The installed runner's real pass/fail/skip JSON is +also checked through the adapter. Vitest 4 includes explicitly skipped assertions +in `numPendingTests`; the adapter accepts that documented-by-output shape and +the prior zero-pending shape, while unfinished assertions and required skips +still fail. Optional skips remain skipped and never become passes. + +`npm run test:toolchain` uses an owned browser/backend/build fixture and proves +React hot reload preserves state and document identity, HTTP and GraphQL +WebSocket proxy paths, denied `.env` content, a compiled offline shell that +works offline, and exclusion of API/auth responses from shell caching. It runs +as a required Smoke Gate step. The normal application, LOD, mobile and runtime +checks remain required too. + +Retained failures and corrections: + +1. Initial build lost DOM matcher types with nested runner installations; + the supported matcher import and deduplicated runner fixed resolution. +2. Initial Vitest 4 discovery ran 26 compiled duplicate server cases; + explicit dist exclusion restores source-only discovery, not fewer source tests. +3. Unbounded coverage hit the editor fixture's five-second budget. Bounded + workers passed on Node 22, but Node 24 still exposed costly repeated role + queries. Direct accessible-label queries preserve all lifecycle assertions; + the corrected 18-case file and complete web suite passed with unchanged budgets. +4. Node 24 HTTP CAPTCHA coverage hit 20 seconds while using the asynchronous + WebCrypto search loop. The independent native-hash fixture solver passed all + four cases without changing the issued work factor or production code. +5. A local real-DB run was blocked in six cases by the host CPU monitor at + 95–100% load. The full 136-case CI-mode run passed; this proves CI parity, + not production CPU-exhaustion behavior. The failed run is retained separately. +6. Initial dependency guards assumed a coverage-package engine field and an + exported React-plugin manifest. Corrected guards check the actual contracts; + they do not weaken supported-runner or version checks. + +7. Real Vitest 4 intentional-skip output initially produced a counter mismatch + for an optional sequence. Explicit skipped-assertion accounting corrected + it; the regression failed before the fix. Real pass/failure/required-skip/ + optional-skip controls now preserve all four outcomes. + +Fresh isolated-cache audit: full inventory has three residual package entries +(Turbo moderate, Mermaid/KaTeX low); production-only has zero. Historical counts +remain dated evidence, not distinct exploit counts or a claim about why registry +metadata changed. Runtime images can still contain development packages and +require the separate packaging and image inventory work in #185. + +Vite's existing all-interface development binding is retained for the repo's +LAN/container workflows. Keep access within an intentionally trusted development +network or authenticated tunnel; do not expose Vite/Vitest UI/API as a public +production service. No host allowlist, credential scope or deployment is changed. diff --git a/docs/quality/images/dynamic-lod-20261027-desktop.png b/docs/quality/images/dynamic-lod-20261027-desktop.png new file mode 100644 index 00000000..6c754d62 Binary files /dev/null and b/docs/quality/images/dynamic-lod-20261027-desktop.png differ diff --git a/docs/quality/images/dynamic-lod-20261027-phone.png b/docs/quality/images/dynamic-lod-20261027-phone.png new file mode 100644 index 00000000..0f8863bb Binary files /dev/null and b/docs/quality/images/dynamic-lod-20261027-phone.png differ diff --git a/docs/quality/schematic-routing-study/chain.svg b/docs/quality/schematic-routing-study/chain.svg new file mode 100644 index 00000000..c26f7464 --- /dev/null +++ b/docs/quality/schematic-routing-study/chain.svg @@ -0,0 +1 @@ +GRAPHDONE / CONNECTION STUDYA clear service pathQueue → Worker · 16 exact relationshipsClient → Gateway · 12 exact relationshipsGateway → Queue · 8 exact relationshipsClient0 in · 12 outGateway12 in · 8 outQueue8 in · 16 outWorker16 in · 0 out36 relationships · 3 routed branchesAll relationships represented · no unrelated crossings \ No newline at end of file diff --git a/docs/quality/schematic-routing-study/dense-cycle.svg b/docs/quality/schematic-routing-study/dense-cycle.svg new file mode 100644 index 00000000..f2439c47 --- /dev/null +++ b/docs/quality/schematic-routing-study/dense-cycle.svg @@ -0,0 +1 @@ +GRAPHDONE / CONNECTION STUDYDense systems disclose collapsed connectionsService 1 → Service 3 · 3 exact relationshipsService 1 → Service 6 · 3 exact relationshipsService 2 → Service 5 · 3 exact relationshipsService 2 → Service 8 · 3 exact relationshipsService 3 → Service 1 · 3 exact relationshipsService 3 → Service 4 · 3 exact relationshipsService 3 → Service 7 · 3 exact relationshipsService 1 → Service 2 · 2 exact relationshipsService 2 → Service 1 · 2 exact relationshipsService 2 → Service 4 · 2 exact relationshipsService 2 → Service 7 · 2 exact relationshipsService 3 → Service 6 · 2 exact relationshipsService 4 → Service 8 · 2 exact relationshipsService 114 in · 14 outService 214 in · 14 outService 314 in · 14 outService 414 in · 14 outService 514 in · 14 outService 614 in · 14 outService 714 in · 14 outService 814 in · 14 out112 relationships · 13 routed branches79 relationships collapsed · inspect the complete list \ No newline at end of file diff --git a/docs/quality/schematic-routing-study/fanout.svg b/docs/quality/schematic-routing-study/fanout.svg new file mode 100644 index 00000000..711586ad --- /dev/null +++ b/docs/quality/schematic-routing-study/fanout.svg @@ -0,0 +1 @@ +GRAPHDONE / CONNECTION STUDYOne source, shared bus, exact branchesAPI → Storage · 9 exact relationshipsAPI → Events · 8 exact relationshipsAPI → Search · 7 exact relationshipsAPI → Billing · 6 exact relationshipsAPI0 in · 30 outBilling6 in · 0 outSearch7 in · 0 outEvents8 in · 0 outStorage9 in · 0 out30 relationships · 4 routed branchesAll relationships represented · no unrelated crossings \ No newline at end of file diff --git a/docs/quality/schematic-routing-study/index.html b/docs/quality/schematic-routing-study/index.html new file mode 100644 index 00000000..dbb1c12d --- /dev/null +++ b/docs/quality/schematic-routing-study/index.html @@ -0,0 +1 @@ +GraphDone connection studies

Organized connections, exact source relationships

Small routing studies from GraphDone's candidate production routing module. Shared colors and junction dots mark an intentional source bus. Arrowheads preserve direction. Dense views disclose every collapsed relationship; the exact list remains required for production integration. These are visual fixtures, not a shipped application claim.

Geometry and relationship accounting · Deliberate spaghetti fixture rejected: 2 card intersections, 3 unrelated crossings, 1 unrelated overlaps.

A clear service path

A clear service path

36 represented + 0 collapsed = 36 original relationships.

{"cardIntersections":0,"unrelatedCrossings":0,"unrelatedOverlaps":0,"diagonalSegments":0,"maxBusesPerCell":1}

One source, shared bus, exact branches

One source, shared bus, exact branches

30 represented + 0 collapsed = 30 original relationships.

{"cardIntersections":0,"unrelatedCrossings":0,"unrelatedOverlaps":0,"diagonalSegments":0,"maxBusesPerCell":1}

Dense systems disclose collapsed connections

Dense systems disclose collapsed connections

33 represented + 79 collapsed = 112 original relationships.

{"cardIntersections":0,"unrelatedCrossings":0,"unrelatedOverlaps":0,"diagonalSegments":0,"maxBusesPerCell":2}

Narrow screens keep readable ports

Narrow screens keep readable ports

23 represented + 0 collapsed = 23 original relationships.

{"cardIntersections":0,"unrelatedCrossings":0,"unrelatedOverlaps":0,"diagonalSegments":0,"maxBusesPerCell":1}
\ No newline at end of file diff --git a/docs/quality/schematic-routing-study/metrics.json b/docs/quality/schematic-routing-study/metrics.json new file mode 100644 index 00000000..3a48a552 --- /dev/null +++ b/docs/quality/schematic-routing-study/metrics.json @@ -0,0 +1,105 @@ +{ + "schema": "graphdone.schematic-study/1", + "fixtures": [ + { + "name": "chain", + "title": "A clear service path", + "sourceRelationships": 36, + "routedRelationships": 36, + "collapsedRelationships": 0, + "routedBranches": 3, + "metrics": { + "cardIntersections": 0, + "unrelatedCrossings": 0, + "unrelatedOverlaps": 0, + "diagonalSegments": 0, + "maxBusesPerCell": 1 + }, + "work": { + "attempts": 3, + "expandedStates": 23, + "maxAttempts": 32, + "maxExpandedStates": 6000 + }, + "deferred": {} + }, + { + "name": "fanout", + "title": "One source, shared bus, exact branches", + "sourceRelationships": 30, + "routedRelationships": 30, + "collapsedRelationships": 0, + "routedBranches": 4, + "metrics": { + "cardIntersections": 0, + "unrelatedCrossings": 0, + "unrelatedOverlaps": 0, + "diagonalSegments": 0, + "maxBusesPerCell": 1 + }, + "work": { + "attempts": 4, + "expandedStates": 617, + "maxAttempts": 32, + "maxExpandedStates": 6000 + }, + "deferred": {} + }, + { + "name": "dense-cycle", + "title": "Dense systems disclose collapsed connections", + "sourceRelationships": 112, + "routedRelationships": 33, + "collapsedRelationships": 79, + "routedBranches": 13, + "metrics": { + "cardIntersections": 0, + "unrelatedCrossings": 0, + "unrelatedOverlaps": 0, + "diagonalSegments": 0, + "maxBusesPerCell": 2 + }, + "work": { + "attempts": 32, + "expandedStates": 3495, + "maxAttempts": 32, + "maxExpandedStates": 6000 + }, + "deferred": { + "routing-budget": { + "branches": 43, + "relationships": 79 + } + } + }, + { + "name": "phone", + "title": "Narrow screens keep readable ports", + "sourceRelationships": 23, + "routedRelationships": 23, + "collapsedRelationships": 0, + "routedBranches": 3, + "metrics": { + "cardIntersections": 0, + "unrelatedCrossings": 0, + "unrelatedOverlaps": 0, + "diagonalSegments": 0, + "maxBusesPerCell": 1 + }, + "work": { + "attempts": 3, + "expandedStates": 166, + "maxAttempts": 12, + "maxExpandedStates": 6000 + }, + "deferred": {} + } + ], + "rejectedNegative": { + "cardIntersections": 2, + "unrelatedCrossings": 3, + "unrelatedOverlaps": 1, + "diagonalSegments": 1, + "maxBusesPerCell": 3 + } +} \ No newline at end of file diff --git a/docs/quality/schematic-routing-study/phone.svg b/docs/quality/schematic-routing-study/phone.svg new file mode 100644 index 00000000..f5b2645c --- /dev/null +++ b/docs/quality/schematic-routing-study/phone.svg @@ -0,0 +1 @@ +GRAPHDONE / CONNECTION STUDYNarrow screens keep readable portsGateway → Delivery · 14 exact relationshipsDelivery → Settlement · 7 exact relationshipsSettlement → Gateway · 2 exact relationshipsGateway · API2 in · 14 outDelivery ·Queue14 in · 7 outSettlement ·Workers7 in · 2 out23 relationships · 3 routed branchesAll relationships represented · no unrelated crossings \ No newline at end of file diff --git a/docs/reviews/2026-09-07-state.md b/docs/reviews/2026-09-07-state.md new file mode 100644 index 00000000..13ae50b9 --- /dev/null +++ b/docs/reviews/2026-09-07-state.md @@ -0,0 +1,55 @@ +# GraphDone state and delivery gaps — 2026-09-07 + +The product vision is a living, graph-native workspace: easy capture and editing, visible progress, human/agent peers, dependency-driven coordination, requirements traceability, and accessible operation across devices. The source contract is `docs/USER_STORIES.md`, `docs/design/interaction-model.md`, and the clarified [human-first AI workspace](../design/human-ai-workspace.md). A working graph canvas does not establish completion of the entire app. + +## Branch baseline + +After fetching origin, local dev and origin/dev were `8a7de05`. Origin/main was `775362e`; origin/dev contains 126 commits absent from origin/main, and origin/main contains two merge commits absent from dev. Local main (`943d5fe`) is substantially older. Work in this review branches from dev on `improve/real-workflows-responsive`; no branch was merged or deployed. + +## Evidence and gaps + +| Area | Observed state | Remaining acceptance criteria | +| --- | --- | --- | +| Core canvas | Living node/edge rendering, grow, quick edit, undo, adaptive quality and layout persistence exist. Core five-case smoke gate passed locally against Neo4j. | Full click-budget coverage; recovery from failed mutations; keyboard capture/navigation; complete touch gesture coverage. | +| Responsive editing | Node quick editor was a fixed-width, unbounded popover with tiny controls. This change adds a viewport-bounded surface, phone/short-touch-landscape sheet, 44px controls, focus containment, Escape/click-away exit, one-tap editing and visual-viewport resize handling. | Native iOS/Android keyboard and assistive-technology verification; full functional coverage beyond the covered journeys. | +| Analytics | Formerly hard-coded values, invented trends/votes and nonfunctional time filters. Now reads actual selected-graph work, status progress, unfinished priorities and contributor records; filters and refresh work. Navigation enabled. | Real historical events and voting data before adding trends or consensus metrics. | +| Agents | Replaced the mock agent catalogue with selected-graph AI contributor records, current work and saved notes. No simulated executions or action counts. Navigation enabled for this recorded-work view. | Persisted agent identity/actions, hosted API/MCP parity and truthful capability states. | +| Ontology | Requirement/SATISFIES basics and a coverage view exist. Type and relationship usage counts now come from the selected graph. Navigation wraps into readable phone rows; unavailable schema-creation controls removed. Custom sets and full validation/reporting contract are not complete. | Typed definitions as data, validation, provenance, suspect-link reports and real usage counts. | +| Activity | Replaced randomized comments, invented status/assignment/completion events and simulated live updates with creation, latest-save timestamps and persisted status notes. | Append-only attributed events, evidence, idempotency, durable cursors. Editable notes are not an audit log. | +| Collaboration | Polling supplies some freshness. This is not the specified live presence/cursor/event system. | Membership/roles, live room service, durable resynchronization, attribution and two-client tests. | +| Admin | Route-role guards exist. At least one settings save handler remains TODO. | Each visible setting must persist or be clearly unavailable; permission tests at the API boundary. | +| Cloud | The online implementation has a distinct API/storage contract. | Run the same user journeys against the cloud backend; keep cloud-specific findings in the Cloud repository's `docs/COST_AND_COMPLETENESS_REVIEW.md`. | + +## Validation scope + +- Final type checking: all four packages passed. +- Production build: all four packages passed (existing large-chunk warning). +- Baseline unit tests: Core 41, Server 28, Web 228 passed. MCP run failed with heap exhaustion in the performance-chaos suite; it is not an all-green baseline. +- Web unit suite: 235 tests passed, including recorded-activity determinism/malformed-data tests. New analytics unit tests cover empty graphs, real status accounting, priority ranking and malformed values, contributor identity/deduplication. +- `tests/e2e/graph/responsive-real-workflows.spec.ts` exercises live Neo4j-backed data, filtering, persisted edits, completion and editor bounds at seven desktop/portrait/landscape dimensions. Each fixture is isolated and cleaned up; small-screen runs use touch input. +- Seven headed responsive persistence journeys passed at 320×568, 390×844, 844×390, 768×1024, 1024×768, 1280×720, and 1920×1080. The test now requires SVG nodes to fit inside the viewport, not merely have CSS visibility. Initial camera framing follows settling positions until stable and stops when a person interacts. +- Initial headed phone portrait/landscape screen matrix: 29 passed (28 screens plus auth setup). The expanded seven-resolution sweep ran 99 cases: 98 passed and one Admin contrast failure identified gray text on hover; that contrast has been corrected. Screenshots exposed crowding the earlier page-overflow audit missed; calendar/timeline filters now collapse, dashboard graphics scale, and the landscape header is smaller. This audit still does not prove every control works or every internal region is visually correct. +- Web lint has zero errors and 698 warnings, predominantly existing. No assertion is made that every page or every requirement is finished. + +## Delivery priorities + +1. Make the hosted persistence and authorization contract correct and cost-bounded. +2. Integrate these responsive/live-data changes and test them against the hosted API. +3. Finish membership, agent, ontology and admin behavior with end-to-end evidence per story. +4. Replace full polling with revision/delta synchronization and a live collaboration service. +5. Keep a release gate covering portrait, landscape, desktop, touch, keyboard, offline/error recovery and reduced motion. Mark a story shipped only when its complete acceptance criteria pass. + +## Simplicity pass after product clarification + +- Overview now leads with real progress, attention items and current work. Open an item directly from the summary; advanced distributions expand on demand. +- Board defaults to populated status lanes, with an explicit option to show empty lanes. Keyboard users can open cards with Enter or Space. +- A touch tap on a graph item or a desktop click on its edit icon opens quick edit directly. Both native touch events and browser compatibility-click paths are handled. The regression test permits one opening action, with no interaction retries. +- Short-window navigation scrolls instead of stranding account controls below the screen. The project-health toggle no longer covers calendar header actions. +- Admin role reference collapses, low-contrast labels are brighter, and dialogs are viewport-bounded, scrollable, named, focus-contained and dismissible with Escape. +- Latest headed workflow gate: 10 passed (seven direct-edit/persistence sizes, including open-editor rotation, plus three Admin form sizes). +- Local D1-backed frontend progress/persistence journey passed. Core API progress-to-UI journeys passed on phone and desktop. These verify recorded updates, not autonomous execution. +- User direction: visual/UI/UX correctness and fewer actions take priority now. Free open-source Core remains distinct from a future managed product with advanced gateway/autonomous-agent capabilities. See the updated design contract. + +Focused screen rerun after the Board/Overview/Admin changes: **22 passed** across the seven matrix resolutions (including setup). The initial full sweep failure is retained above to make the audit history clear. + +Overview/Board direct-opening and keyboard journeys: **2 passed** at 360px portrait and 844px landscape. Final production build passes all four packages; the existing large-bundle warning remains. diff --git a/docs/security/authentication-security-guide.md b/docs/security/authentication-security-guide.md index 88b7d6ff..08487e87 100644 --- a/docs/security/authentication-security-guide.md +++ b/docs/security/authentication-security-guide.md @@ -1,5 +1,10 @@ # GraphDone Authentication Security Guide +For dated source/probe evidence and current follow-up priorities, see the +[2026-10-23 security scope](../quality/SECURITY_SCOPE_2026-10-23.md). +This older guide contains historical plans and examples; it is not evidence +that every listed control is implemented or that deployment settings were verified. + ## Overview GraphDone uses a hybrid authentication system with SQLite for user credentials and Neo4j for graph data. This guide documents security practices, identifies vulnerabilities, and provides implementation roadmap. @@ -371,4 +376,4 @@ docker exec -it graphdone-api-prod npm run enable-rate-limiting # Backup authentication database docker run --rm -v sqlite_auth_data:/source -v $(pwd):/backup alpine \ tar czf /backup/auth-emergency-backup.tar.gz -C /source . -``` \ No newline at end of file +``` diff --git a/docs/oauth-implementation.md b/docs/security/oauth-implementation.md similarity index 100% rename from docs/oauth-implementation.md rename to docs/security/oauth-implementation.md diff --git a/docs/oauth-setup-guide.md b/docs/security/oauth-setup-guide.md similarity index 100% rename from docs/oauth-setup-guide.md rename to docs/security/oauth-setup-guide.md diff --git a/docs/oauth-testing-guide.md b/docs/security/oauth-testing-guide.md similarity index 96% rename from docs/oauth-testing-guide.md rename to docs/security/oauth-testing-guide.md index be8958af..fa55d235 100644 --- a/docs/oauth-testing-guide.md +++ b/docs/security/oauth-testing-guide.md @@ -27,7 +27,7 @@ npx playwright test tests/e2e/oauth-linkedin.spec.ts --ui ## What's Included -### 1. Official Documentation (`docs/oauth-implementation.md`) +### 1. Official Documentation (`docs/security/oauth-implementation.md`) **Comprehensive guide with:** - ✅ Official spec links for Google, GitHub, LinkedIn @@ -46,7 +46,7 @@ npx playwright test tests/e2e/oauth-linkedin.spec.ts --ui - Security best practices - Known issues prioritization -### 2. Mock OAuth Server (`tests/helpers/mock-oauth-server.ts`) +### 2. Mock OAuth Server (`tests/lib/mock-oauth-server.ts`) **Full-featured mock server:** - ✅ Simulates Google OAuth 2.0 @@ -306,13 +306,13 @@ LINKEDIN_CALLBACK_URL=https://localhost:4128/auth/linkedin/callback ## Support **Documentation:** -- Implementation guide: `docs/oauth-implementation.md` -- This testing guide: `docs/oauth-testing-guide.md` +- Implementation guide: `docs/security/oauth-implementation.md` +- This testing guide: `docs/security/oauth-testing-guide.md` **Code:** - OAuth strategies: `packages/server/src/auth/oauth-strategies.ts` - OAuth routes: `packages/server/src/routes/auth.ts` -- Mock server: `tests/helpers/mock-oauth-server.ts` +- Mock server: `tests/lib/mock-oauth-server.ts` - Test fixtures: `tests/fixtures/oauth-profiles.ts` - LinkedIn tests: `tests/e2e/oauth-linkedin.spec.ts` diff --git a/docs/simple-agent-reality.md b/docs/simple-agent-reality.md index 4db34712..1168a1a6 100644 --- a/docs/simple-agent-reality.md +++ b/docs/simple-agent-reality.md @@ -1,3 +1,5 @@ +> Historical exploration, superseded by [the current human-first Core/hosted strategy](product/CORE_HOSTED_STRATEGY.md). Do not implement this older pet-agent direction as the product roadmap. + # Simple AI Agent - Reality Check > **🎯 THIS IS THE ACTUAL PLAN** - Start here for AI agents in GraphDone diff --git a/docs/test-organization.md b/docs/test-organization.md deleted file mode 100644 index 20372345..00000000 --- a/docs/test-organization.md +++ /dev/null @@ -1,95 +0,0 @@ -# Test File Organization - -## Directory Structure - -After cleanup, all test-related files are properly organized: - -### 📁 `tests/` -All test scripts and test runners: -- `run-all-tests.js` - Main test runner -- `simple-login-test.js` - Login functionality tests -- `https-browser-compatibility-test.js` - HTTPS compatibility tests -- `mobile-https-compatibility-test.js` - Mobile HTTPS tests -- `realtime-update-test.js` - Real-time update tests -- `ssl-certificate-analysis.js` - SSL certificate analysis -- E2E test specs (`*.spec.ts`) -- Test helpers and utilities - -### 📁 `test-results/` -All test outputs and results: -- `reports/` - Test report documents (*.md) - - PR validation reports - - Test analysis documents - - Actual test results -- `installation/` - Installation test logs -- `macos-installation/` - macOS-specific test results -- HTML reports (`*_report_*.html`) -- JSON test data - -### 📁 `artifacts/` -Test artifacts and temporary files: -- `screenshots/` - All test screenshots (*.png) - - Login screenshots - - HTTPS test captures - - Mobile compatibility screenshots - - SSL test images - -### 📁 `scripts/` -Shell scripts for testing: -- Installation test scripts -- Report generation scripts -- Test utilities -- `Makefile` for test commands - -## .gitignore Rules - -Added rules to prevent future clutter in root: -```gitignore -# Test files that should not be in root -/*.test.js -/*.spec.js -/*-test.js -/*-spec.js -/test-*.js -/test-*.md -/*TEST*.md -/*REPORT*.md - -# Temporary test outputs -/clean_report_*.html -/comprehensive_report_*.html -/final_report_*.html -/report_*.html -``` - -## Cleanup Summary - -**Moved from root directory:** -- ✅ 6 test report documents → `test-results/reports/` -- ✅ 6 JavaScript test files → `tests/` -- ✅ 25 PNG screenshots → `artifacts/screenshots/` -- ✅ 1 Makefile → `scripts/` - -**Root directory now contains only:** -- Essential config files (package.json, tsconfig.json, etc.) -- Documentation (README.md, CLAUDE.md, LICENSE) -- Git configuration files -- No test artifacts or temporary files - -## Future Test Output Guidelines - -When creating test outputs: - -1. **Test Scripts**: Place in `tests/` directory -2. **Test Reports**: Save to `test-results/reports/` -3. **Screenshots**: Save to `artifacts/screenshots/` -4. **HTML Reports**: Save to `test-results/` with timestamp -5. **Temporary Files**: Use `test-results/` or system temp directory -6. **Never**: Save test outputs directly to root directory - -This organization ensures: -- Clean repository root -- Easy to find test artifacts -- Clear separation of concerns -- Better .gitignore management -- Professional repository structure \ No newline at end of file diff --git a/docs/test-tree-refactor-plan.json b/docs/test-tree-refactor-plan.json new file mode 100644 index 00000000..b843ac3a --- /dev/null +++ b/docs/test-tree-refactor-plan.json @@ -0,0 +1,592 @@ +{ + "summary": "Inventory the sprawling test surface (both repos) and synthesize a clean target tree + migration plan + unified dual-output harness design", + "agentCount": 7, + "logs": [], + "result": { + "targetTree": "CANONICAL CONFIG STAYS AT REPO ROOT: /playwright.config.ts (the one config; tests/playwright.config.ts + tests/playwright.config.production.ts deleted). Playwright auto-discovers it from cwd; its projects point testDir at the folders below.\n\ntests/\n├── run-unified.mjs # SINGLE entry → npm run test:unified (report.html + report.json)\n├── sequences/\n│ └── unified.config.mjs # declarative manifest of sequences + profiles (smoke|pr|full|report)\n├── e2e/ # default PW project testDir; grouped by domain\n│ ├── smoke/\n│ │ └── user-smoke.spec.ts # THE GATE (@smoke) — kept runnable\n│ ├── auth/\n│ │ ├── form-validation.spec.ts\n│ │ ├── responsive-layout.spec.ts (was signup-fit)\n│ │ └── passwordless-focus.spec.ts (was login-focus)\n│ ├── graph/\n│ │ ├── camera.spec.ts\n│ │ ├── living-graph-effects.spec.ts (was living-graph)\n│ │ ├── relationship-flip.spec.ts\n│ │ ├── node-quick-edit.spec.ts\n│ │ └── node-inspector-copyid.spec.ts (was copy-id)\n│ ├── ui/\n│ │ ├── overlay-dismissal.spec.ts\n│ │ └── z-order.spec.ts\n│ ├── a11y/\n│ │ └── modal-focus.spec.ts (was a11y-focus)\n│ ├── mobile/\n│ │ ├── experience.spec.ts\n│ │ ├── view-navigation.spec.ts (was mobile-views)\n│ │ ├── accessibility-audit.spec.ts (was mobile-audit)\n│ │ ├── dialog-positioning.spec.ts (was mobile-dialogs)\n│ │ └── tls-compat.spec.ts (ported from mobile-https-compatibility-test.js)\n│ ├── responsive/\n│ │ └── viewports.spec.ts (was responsive)\n│ ├── api/\n│ │ └── health.spec.ts (was api-health)\n│ ├── admin/\n│ │ ├── database-tab.spec.ts (was admin-database-tab)\n│ │ └── oauth-provider-config.spec.ts\n│ └── reports/ # capture-heavy, own PW projects, report-only\n│ ├── showcase.spec.ts # records .webm + screenshots (video source)\n│ ├── feature-matrix.spec.ts\n│ ├── matrix.setup.ts\n│ ├── visual-regression.spec.ts (was visual-regression-suite)\n│ └── vlm-evaluation.spec.ts (was visual-vlm)\n├── perf/ # perf + perf-scale PW projects\n│ ├── graph-perf.spec.ts # budget gate (@perf)\n│ ├── scale-sweep.spec.ts\n│ └── check-bundle-size.mjs\n├── diagnostics/ # geometry PW project (recursive testDir)\n│ ├── layout/\n│ │ ├── geometry.spec.ts (was graph-geometry)\n│ │ └── graph-balance.spec.ts\n│ ├── interactions/\n│ │ ├── core-matrix.spec.ts (was core-interactions)\n│ │ └── relationship-audit.spec.ts (was interaction-audit)\n│ ├── hierarchy/\n│ │ ├── explorer-tree.spec.ts\n│ │ └── navigation.spec.ts (was hierarchy-navigation)\n│ ├── inspector/\n│ │ └── node-inspector.spec.ts\n│ ├── physics/\n│ │ └── settle.spec.ts (was physics-settle)\n│ ├── perf/\n│ │ └── large-graph-profile.spec.ts\n│ └── ui/\n│ ├── legibility-floor.spec.ts (was node-expand-legibility)\n│ ├── minimap-zoom.spec.ts\n│ ├── captcha-layout.spec.ts\n│ └── tls-banner.spec.ts (was insecure-connection-banner)\n├── integration/ # non-app infra specs (excluded from default project)\n│ ├── tls-integration.spec.ts\n│ ├── tls-certificate-cross-browser.spec.ts (ported from ssl-certificate-analysis.js)\n│ └── installation-validation.spec.ts\n├── fixtures/\n│ └── oauth-profiles.ts\n└── lib/ # REUSABLE harness + helpers (shared by Core AND Cloud)\n ├── README.md\n ├── auth.ts (from helpers/) # most-imported helper — battle-tested login\n ├── api.ts (from helpers/)\n ├── seedGraph.ts (from helpers/)\n ├── dbHealing.ts (from helpers/)\n ├── testEnv.ts (from helpers/)\n ├── mobileAudit.ts (from helpers/)\n ├── zorder.ts (from helpers/)\n ├── vlm.ts (from helpers/)\n ├── mock-oauth-server.ts (from helpers/)\n ├── metrics/\n │ └── balance_metrics.py (from helpers/) # subprocess; Cloud audit-kit BALANCE_PY cross-links here\n ├── audit/ # promoted from GraphDone-Cloud/scripts/lib (Cloud cross-links back)\n │ ├── audit-kit.mjs\n │ ├── schema-contract.mjs\n │ └── schema-coverage.mjs\n ├── reporting/\n │ ├── aggregate.mjs (from tests/_draft_unified/aggregate.mjs) # pure rollup\n │ ├── html.mjs # unified HTML report: screenshots +