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..e02c4447 --- /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 Graphlings' `.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/issues/INDEX.md b/.agents/issues/INDEX.md new file mode 100644 index 00000000..73290412 --- /dev/null +++ b/.agents/issues/INDEX.md @@ -0,0 +1,18 @@ +# Symptom index + +| Symptom | Canonical lesson and regression | +| --- | --- | +| 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` | +| 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) | 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..1ad1d38c --- /dev/null +++ b/.agents/issues/magic-link-index.md @@ -0,0 +1,6 @@ +# Magic-link symptom index + +| Symptom | Evidence and corrective rule | +| --- | --- | +| 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..23de511d --- /dev/null +++ b/.agents/issues/performance-evidence-index.md @@ -0,0 +1,6 @@ +# Performance evidence symptoms + +| Symptom | Lesson and regression | +| --- | --- | +| 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..039affee --- /dev/null +++ b/.agents/issues/security-audit-index.md @@ -0,0 +1,9 @@ +# Security audit symptom index + +| Symptom | Evidence and corrective rule | +| --- | --- | +| 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) | 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 ` and +`git diff --raw --abbrev=40 `; 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 --repo GraphDone/GraphDone-Core --merge --admin --match-head-commit +``` + +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/.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..71ada327 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,14 @@ 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 # ── The real unit suites, one job per package, run in parallel ───────────── unit-tests: @@ -54,9 +51,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 +78,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 +89,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 +105,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 +122,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,7 +164,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 (dev runtime imports built @graphdone/core) run: npm run build - name: Install Playwright (chromium) @@ -179,17 +178,27 @@ 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: 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: 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 +250,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 +272,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 +285,41 @@ 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 + - 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 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 - 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 # ── 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] 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,6 +328,7 @@ jobs: && "${{ needs.unit-tests.result }}" == "success" \ && "${{ needs.mcp-contract.result }}" == "success" \ && "${{ needs.build.result }}" == "success" \ + && "${{ needs.security-scan.result }}" == "success" \ && "${{ needs.smoke-gate.result }}" == "success" ]]; then echo "✅ CI green — lint, types, every unit suite, the build, and the user smoke gate all passed." else diff --git a/.github/workflows/comprehensive-tests.yml b/.github/workflows/comprehensive-tests.yml index c6d1b83b..b20f98af 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,18 +26,17 @@ 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 && npm run test:unified:lib # Genuinely-real checks: the installer exists, deployment compose parses installer-and-deploy-config: @@ -75,7 +73,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 index 73d743ab..5ff64e75 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,165 +1,16 @@ -name: Deploy +name: Deploy (disabled) on: - push: - branches: [main] - tags: ['v*'] - workflow_run: - workflows: [CI] - branches: [main] - types: [completed] + workflow_dispatch: -env: - NODE_VERSION: '20' - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} +permissions: {} jobs: - deploy-staging: - name: Deploy to Staging + unavailable: + name: Deploy is not configured 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 + - name: Reject unimplemented deploy operation 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 + echo "::error::Deploy is disabled. See docs/quality/RELEASE_GATES.md for the required validated-SHA release process." + exit 1 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-`; 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..c5c957c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,225 +1,16 @@ -name: Release +name: Release (disabled) 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' +permissions: {} jobs: - validate: - name: Validate Release + unavailable: + name: Release is not configured runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 - - - 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 - - - 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 - - test: - name: Run Full Test Suite - 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 - - 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: Run linting - run: npm run lint - - - name: Run type checking - run: npm run typecheck - - - name: Run tests - run: npm run test - env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/graphdone_test - - - name: Build packages - run: npm run build - - create-release: - name: Create Release - runs-on: ubuntu-latest - needs: test - outputs: - release-id: ${{ steps.create-release.outputs.id }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - 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: 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 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - tags: true - - - name: Create GitHub Release - id: create-release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - 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: Upload source archive - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - 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 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - 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' - 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' + - name: Reject unimplemented release operation run: | - echo "❌ Release v${{ github.event.inputs.version }} failed!" - # Add failure notification logic here \ No newline at end of file + echo "::error::Release is disabled. See docs/quality/RELEASE_GATES.md for the required validated-SHA release process." + exit 1 diff --git a/.gitignore b/.gitignore index d4e8400d..3d7f9a64 100644 --- a/.gitignore +++ b/.gitignore @@ -117,4 +117,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..ae55fe17 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,23 @@ +# 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-.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. diff --git a/CLAUDE.md b/CLAUDE.md index 349f6936..c6baae99 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 development (v0.3.1-alpha) 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,14 +172,41 @@ 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/`), +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//`, `tests/diagnostics//`, `tests/integration/`, with +all reusable helpers + harness modules under `tests/lib/`. Retired/legacy specs are +parked (not deleted) under `archive/2026-06-test-cleanup/` — see its MANIFEST.md. **🔐 HTTPS/TLS Testing Setup (for next developer):** ```bash @@ -281,7 +313,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 +389,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 @@ -422,16 +454,21 @@ artifacts/ └── certificates/ # Test certificates 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// # smoke, auth, graph, ui, a11y, mobile, responsive, api, admin, reports +├── diagnostics// # 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 canonical `playwright.config.ts` at the repo root; the unified harness is the single test entry. +- All reusable helpers + harness modules live under `tests/lib/`; specs import `../../lib/` (depth-2 domain folders). +- Capture-heavy report-only specs live in `tests/e2e/reports/` (own Playwright projects), excluded from the fast default project. +- Retired/legacy specs are archived (not deleted) in `archive/2026-06-test-cleanup/` with a MANIFEST — mine before deleting. +- No loose files at repository root. ## Key Implementation Guidelines @@ -454,7 +491,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 +546,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..0ff4c639 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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) @@ -12,7 +12,7 @@ ## 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,6 +45,10 @@ 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!) @@ -61,12 +65,12 @@ wget -qO- https://raw.githubusercontent.com/GraphDone/GraphDone-Core/main/public **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:** @@ -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 @@ -185,7 +189,7 @@ 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?** 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://:3127 - Web UI -# http://:4127 - GraphQL API -# http://: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/archive/2026-06-test-cleanup/MANIFEST.md b/archive/2026-06-test-cleanup/MANIFEST.md new file mode 100644 index 00000000..b29e38c0 --- /dev/null +++ b/archive/2026-06-test-cleanup/MANIFEST.md @@ -0,0 +1,66 @@ +# Archived test files — 2026-06 test-tree cleanup + +**Status: flagged for deletion.** These were identified as dead / stale / duplicate +/ placeholder during the clean-tree test refactor (see `docs/test-tree-refactor-plan.json`). +They are **archived, not deleted**, so we can mine them for any salvageable code, +selectors, or test data before removing them for good. Original repo-relative paths +are preserved under this folder. Nothing here is run by Playwright, vitest, CI, or +any npm script (references were repointed when these moved). + +When you're satisfied nothing of value remains, delete `archive/2026-06-test-cleanup/`. + +## E2E specs (redundant / stale / placeholder) +| file | why archived | +| --- | --- | +| tests/e2e/auth-basic-test.spec.ts | redundant admin-login; covered by form-validation/mobile-audit | +| tests/e2e/auth-system-test.spec.ts | vague auth validation; form-validation is more specific | +| tests/e2e/add-node.spec.ts | overlaps the user-smoke grow flow | +| tests/e2e/comprehensive-interaction.spec.ts | vague exploratory; overlaps diagnostics core-matrix | +| tests/e2e/comprehensive-graph-operations.spec.ts | vague; overlaps living-graph + real-time | +| tests/e2e/database-connectivity.spec.ts | Neo4j-only failure path obsolete under D1-first arch | +| tests/e2e/fix-left-click-refresh.spec.ts | one-off bug proof, untagged, unreferenced | +| tests/e2e/flash-detection.spec.ts | exploratory, untagged; overlaps showcase | +| tests/e2e/graph-error-handling.spec.ts | data template, not a real test; schema-mismatched | +| tests/e2e/graph-real-time-updates.spec.ts | unclear purpose; overlaps living-graph | +| tests/e2e/manual-test.spec.ts | manual placeholder, no assertions | +| tests/e2e/neo4j-core-functionality.spec.ts | redundant with @core; untagged | +| tests/e2e/neo4j-working-demo.spec.ts | old demo proof; overlaps user-smoke | +| tests/e2e/oauth-linkedin.spec.ts | stale OAuth provider (magic-link now); mock/fixtures retained for admin spec | +| tests/e2e/project-name-verification.spec.ts | exploratory naming check, untagged | +| tests/e2e/screenshot-current-graph.spec.ts | documentation artifact; overlaps showcase | +| tests/e2e/simple-error-test.spec.ts | template placeholder, no real assertions | +| tests/e2e/ui-basic-functionality.spec.ts | vague; overlaps responsive | +| tests/e2e/user-workflow.spec.ts | overlaps user-smoke/basic-workflow, untagged | +| tests/e2e/user-workflow-robust.spec.ts | overlaps user-smoke, untagged | +| tests/e2e/verify-improved-visualization.spec.ts | old proof; overlaps user-smoke/showcase | +| tests/e2e/verify-ui-data.spec.ts | generic proof, no real assertions | +| tests/e2e/workspace-scrolling.spec.ts | old issue proof, no real assertions | + +## Loose runners / debug scripts / configs (superseded) +| file | why archived | +| --- | --- | +| tests/run-all-tests.js | replaced by tests/run-unified.mjs (npm run test:comprehensive → unified) | +| tests/run-pr-tests.js | replaced by tests/run-unified.mjs --profile pr | +| tests/ci-basic-tests.js | mock-only fake results; CI runs real Playwright now | +| tests/quick-auth-test.js | manual debug tool, hardcoded creds | +| tests/simple-login-test.js | manual debug tool, infinite wait | +| tests/manual-graph-operations-test.js | manual debug tool | +| tests/realtime-update-test.js | manual debug tool | +| tests/test-production-scrolling.js | manual debug tool, hardcoded :3128 | +| tests/test-refresh-bugs.js | manual debug tool | +| tests/https-browser-compatibility-test.js | overlaps ported TLS specs; unreferenced | +| tests/global-setup-production.js | dead binding (only the production config referenced it) | +| tests/playwright.config.ts | duplicate config, referenced by nothing | +| tests/playwright.config.production.ts | settings to be folded into root config | + +## Redundant shell scripts +| file | why archived | +| --- | --- | +| scripts/generate-final-report.sh | duplicate installation-report generator (keep generate-clean-report.sh) | +| scripts/generate-comprehensive-report.sh | duplicate installation-report generator | +| scripts/test-cert-security.sh | stale, not in CI; covered by test-tls.sh | +| scripts/test-installation-demo.sh | duplicate of test-installation-simple.sh | +| scripts/test-installation-full.sh | duplicate of test-installation-simple.sh | +| scripts/test-installation-functional.sh | duplicate of test-installation-simple.sh | +| scripts/test-installation-macos.sh | single-platform, not in CI | +| scripts/test-installation-multi-distro.sh | deprecated; Docker approach in test-installation-simple.sh | diff --git a/docs/pr-testing-guide.md b/archive/2026-06-test-cleanup/docs/testing/pr-testing-guide.md similarity index 95% rename from docs/pr-testing-guide.md rename to archive/2026-06-test-cleanup/docs/testing/pr-testing-guide.md index fd8d5290..7accede4 100644 --- a/docs/pr-testing-guide.md +++ b/archive/2026-06-test-cleanup/docs/testing/pr-testing-guide.md @@ -290,7 +290,7 @@ docker logs graphdone-neo4j ### OAuth Test Maintenance -See [docs/oauth-testing-guide.md](./oauth-testing-guide.md) for OAuth-specific maintenance: +See [docs/oauth-testing-guide.md](../security/oauth-testing-guide.md) for OAuth-specific maintenance: - Monthly spec review (every 12th) - Provider documentation updates - Token handling validation @@ -332,7 +332,7 @@ See [docs/oauth-testing-guide.md](./oauth-testing-guide.md) for OAuth-specific m ### Writing Reliable Tests -- ✅ **Use auth helpers** - Leverage `tests/helpers/auth.ts` for consistent login +- ✅ **Use auth helpers** - Leverage `tests/lib/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 @@ -371,14 +371,14 @@ 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 +- [OAuth Testing Guide](../security/oauth-testing-guide.md) - OAuth-specific testing +- [OAuth Implementation Guide](../security/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` +- Auth helpers: `tests/lib/auth.ts` +- OAuth mock server: `tests/lib/mock-oauth-server.ts` - Test fixtures: `tests/fixtures/oauth-profiles.ts` - E2E tests: `tests/e2e/*.spec.ts` diff --git a/docs/test-organization.md b/archive/2026-06-test-cleanup/docs/testing/test-organization.md similarity index 100% rename from docs/test-organization.md rename to archive/2026-06-test-cleanup/docs/testing/test-organization.md diff --git a/docs/testing-architecture.md b/archive/2026-06-test-cleanup/docs/testing/testing-architecture.md similarity index 100% rename from docs/testing-architecture.md rename to archive/2026-06-test-cleanup/docs/testing/testing-architecture.md diff --git a/scripts/generate-comprehensive-report.sh b/archive/2026-06-test-cleanup/scripts/generate-comprehensive-report.sh similarity index 100% rename from scripts/generate-comprehensive-report.sh rename to archive/2026-06-test-cleanup/scripts/generate-comprehensive-report.sh diff --git a/scripts/generate-final-report.sh b/archive/2026-06-test-cleanup/scripts/generate-final-report.sh similarity index 100% rename from scripts/generate-final-report.sh rename to archive/2026-06-test-cleanup/scripts/generate-final-report.sh diff --git a/scripts/test-cert-security.sh b/archive/2026-06-test-cleanup/scripts/test-cert-security.sh similarity index 100% rename from scripts/test-cert-security.sh rename to archive/2026-06-test-cleanup/scripts/test-cert-security.sh diff --git a/scripts/test-installation-demo.sh b/archive/2026-06-test-cleanup/scripts/test-installation-demo.sh similarity index 100% rename from scripts/test-installation-demo.sh rename to archive/2026-06-test-cleanup/scripts/test-installation-demo.sh diff --git a/scripts/test-installation-full.sh b/archive/2026-06-test-cleanup/scripts/test-installation-full.sh similarity index 100% rename from scripts/test-installation-full.sh rename to archive/2026-06-test-cleanup/scripts/test-installation-full.sh diff --git a/scripts/test-installation-functional.sh b/archive/2026-06-test-cleanup/scripts/test-installation-functional.sh similarity index 100% rename from scripts/test-installation-functional.sh rename to archive/2026-06-test-cleanup/scripts/test-installation-functional.sh diff --git a/scripts/test-installation-macos.sh b/archive/2026-06-test-cleanup/scripts/test-installation-macos.sh similarity index 100% rename from scripts/test-installation-macos.sh rename to archive/2026-06-test-cleanup/scripts/test-installation-macos.sh diff --git a/scripts/test-installation-multi-distro.sh b/archive/2026-06-test-cleanup/scripts/test-installation-multi-distro.sh similarity index 100% rename from scripts/test-installation-multi-distro.sh rename to archive/2026-06-test-cleanup/scripts/test-installation-multi-distro.sh diff --git a/tests/ci-basic-tests.js b/archive/2026-06-test-cleanup/tests/ci-basic-tests.js similarity index 100% rename from tests/ci-basic-tests.js rename to archive/2026-06-test-cleanup/tests/ci-basic-tests.js diff --git a/tests/e2e/add-node.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/add-node.spec.ts similarity index 100% rename from tests/e2e/add-node.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/add-node.spec.ts diff --git a/tests/e2e/auth-basic-test.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/auth-basic-test.spec.ts similarity index 100% rename from tests/e2e/auth-basic-test.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/auth-basic-test.spec.ts diff --git a/tests/e2e/auth-system-test.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/auth-system-test.spec.ts similarity index 100% rename from tests/e2e/auth-system-test.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/auth-system-test.spec.ts diff --git a/tests/e2e/comprehensive-graph-operations.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/comprehensive-graph-operations.spec.ts similarity index 100% rename from tests/e2e/comprehensive-graph-operations.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/comprehensive-graph-operations.spec.ts diff --git a/tests/e2e/comprehensive-interaction.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/comprehensive-interaction.spec.ts similarity index 100% rename from tests/e2e/comprehensive-interaction.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/comprehensive-interaction.spec.ts diff --git a/tests/e2e/database-connectivity.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/database-connectivity.spec.ts similarity index 100% rename from tests/e2e/database-connectivity.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/database-connectivity.spec.ts diff --git a/tests/e2e/fix-left-click-refresh.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/fix-left-click-refresh.spec.ts similarity index 100% rename from tests/e2e/fix-left-click-refresh.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/fix-left-click-refresh.spec.ts diff --git a/tests/e2e/flash-detection.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/flash-detection.spec.ts similarity index 100% rename from tests/e2e/flash-detection.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/flash-detection.spec.ts diff --git a/tests/e2e/graph-error-handling.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/graph-error-handling.spec.ts similarity index 100% rename from tests/e2e/graph-error-handling.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/graph-error-handling.spec.ts diff --git a/tests/e2e/graph-real-time-updates.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/graph-real-time-updates.spec.ts similarity index 100% rename from tests/e2e/graph-real-time-updates.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/graph-real-time-updates.spec.ts diff --git a/tests/e2e/manual-test.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/manual-test.spec.ts similarity index 100% rename from tests/e2e/manual-test.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/manual-test.spec.ts diff --git a/tests/e2e/neo4j-core-functionality.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/neo4j-core-functionality.spec.ts similarity index 100% rename from tests/e2e/neo4j-core-functionality.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/neo4j-core-functionality.spec.ts diff --git a/tests/e2e/neo4j-working-demo.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/neo4j-working-demo.spec.ts similarity index 100% rename from tests/e2e/neo4j-working-demo.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/neo4j-working-demo.spec.ts diff --git a/tests/e2e/oauth-linkedin.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/oauth-linkedin.spec.ts similarity index 100% rename from tests/e2e/oauth-linkedin.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/oauth-linkedin.spec.ts diff --git a/tests/e2e/project-name-verification.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/project-name-verification.spec.ts similarity index 100% rename from tests/e2e/project-name-verification.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/project-name-verification.spec.ts diff --git a/tests/e2e/screenshot-current-graph.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/screenshot-current-graph.spec.ts similarity index 100% rename from tests/e2e/screenshot-current-graph.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/screenshot-current-graph.spec.ts diff --git a/tests/e2e/simple-error-test.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/simple-error-test.spec.ts similarity index 100% rename from tests/e2e/simple-error-test.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/simple-error-test.spec.ts diff --git a/tests/e2e/ui-basic-functionality.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/ui-basic-functionality.spec.ts similarity index 100% rename from tests/e2e/ui-basic-functionality.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/ui-basic-functionality.spec.ts diff --git a/tests/e2e/user-workflow-robust.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/user-workflow-robust.spec.ts similarity index 100% rename from tests/e2e/user-workflow-robust.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/user-workflow-robust.spec.ts diff --git a/tests/e2e/user-workflow.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/user-workflow.spec.ts similarity index 100% rename from tests/e2e/user-workflow.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/user-workflow.spec.ts diff --git a/tests/e2e/verify-improved-visualization.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/verify-improved-visualization.spec.ts similarity index 100% rename from tests/e2e/verify-improved-visualization.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/verify-improved-visualization.spec.ts diff --git a/tests/e2e/verify-ui-data.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/verify-ui-data.spec.ts similarity index 100% rename from tests/e2e/verify-ui-data.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/verify-ui-data.spec.ts diff --git a/tests/e2e/workspace-scrolling.spec.ts b/archive/2026-06-test-cleanup/tests/e2e/workspace-scrolling.spec.ts similarity index 100% rename from tests/e2e/workspace-scrolling.spec.ts rename to archive/2026-06-test-cleanup/tests/e2e/workspace-scrolling.spec.ts diff --git a/tests/global-setup-production.js b/archive/2026-06-test-cleanup/tests/global-setup-production.js similarity index 100% rename from tests/global-setup-production.js rename to archive/2026-06-test-cleanup/tests/global-setup-production.js diff --git a/tests/https-browser-compatibility-test.js b/archive/2026-06-test-cleanup/tests/https-browser-compatibility-test.js similarity index 100% rename from tests/https-browser-compatibility-test.js rename to archive/2026-06-test-cleanup/tests/https-browser-compatibility-test.js diff --git a/tests/manual-graph-operations-test.js b/archive/2026-06-test-cleanup/tests/manual-graph-operations-test.js similarity index 100% rename from tests/manual-graph-operations-test.js rename to archive/2026-06-test-cleanup/tests/manual-graph-operations-test.js diff --git a/tests/playwright.config.production.ts b/archive/2026-06-test-cleanup/tests/playwright.config.production.ts similarity index 100% rename from tests/playwright.config.production.ts rename to archive/2026-06-test-cleanup/tests/playwright.config.production.ts diff --git a/tests/playwright.config.ts b/archive/2026-06-test-cleanup/tests/playwright.config.ts similarity index 100% rename from tests/playwright.config.ts rename to archive/2026-06-test-cleanup/tests/playwright.config.ts diff --git a/tests/quick-auth-test.js b/archive/2026-06-test-cleanup/tests/quick-auth-test.js similarity index 100% rename from tests/quick-auth-test.js rename to archive/2026-06-test-cleanup/tests/quick-auth-test.js diff --git a/tests/realtime-update-test.js b/archive/2026-06-test-cleanup/tests/realtime-update-test.js similarity index 100% rename from tests/realtime-update-test.js rename to archive/2026-06-test-cleanup/tests/realtime-update-test.js diff --git a/tests/run-all-tests.js b/archive/2026-06-test-cleanup/tests/run-all-tests.js similarity index 99% rename from tests/run-all-tests.js rename to archive/2026-06-test-cleanup/tests/run-all-tests.js index 0b116eae..efd80d7e 100644 --- a/tests/run-all-tests.js +++ b/archive/2026-06-test-cleanup/tests/run-all-tests.js @@ -735,7 +735,7 @@ function generateHTMLReport() {

GraphDone Test Report

-
Comprehensive testing results for graph-native project management
+
Comprehensive testing results for graph-native management
Generated: ${new Date().toLocaleString()} | Environment: ${testResults.environment} | diff --git a/tests/run-pr-tests.js b/archive/2026-06-test-cleanup/tests/run-pr-tests.js similarity index 100% rename from tests/run-pr-tests.js rename to archive/2026-06-test-cleanup/tests/run-pr-tests.js diff --git a/tests/simple-login-test.js b/archive/2026-06-test-cleanup/tests/simple-login-test.js similarity index 100% rename from tests/simple-login-test.js rename to archive/2026-06-test-cleanup/tests/simple-login-test.js diff --git a/tests/test-production-scrolling.js b/archive/2026-06-test-cleanup/tests/test-production-scrolling.js similarity index 100% rename from tests/test-production-scrolling.js rename to archive/2026-06-test-cleanup/tests/test-production-scrolling.js diff --git a/tests/test-refresh-bugs.js b/archive/2026-06-test-cleanup/tests/test-refresh-bugs.js similarity index 100% rename from tests/test-refresh-bugs.js rename to archive/2026-06-test-cleanup/tests/test-refresh-bugs.js diff --git a/deployment/Dockerfile b/deployment/Dockerfile index e80d9613..26ea6d33 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -1,5 +1,5 @@ # Multi-stage build for production -FROM node:18-alpine AS base +FROM node:22-alpine AS base # Install dependencies only when needed FROM base AS deps @@ -7,17 +7,19 @@ RUN apk add --no-cache libc6-compat WORKDIR /app # Install dependencies based on the preferred package manager -COPY package.json turbo.json ./ +COPY package.json package-lock.json turbo.json ./ COPY packages/core/package.json ./packages/core/ COPY packages/server/package.json ./packages/server/ COPY packages/web/package.json ./packages/web/ +COPY packages/mcp-server/package.json ./packages/mcp-server/ -RUN npm ci --only=production +RUN npm ci --legacy-peer-deps # Build the source code FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/packages ./packages COPY . . # Build all packages @@ -34,12 +36,15 @@ 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/package.json ./package.json +COPY --from=builder --chown=graphdone:nodejs /app/packages/core ./packages/core +COPY --from=builder --chown=graphdone:nodejs /app/packages/server ./packages/server COPY --from=builder --chown=graphdone:nodejs /app/packages/web/dist ./web COPY --from=deps --chown=graphdone:nodejs /app/node_modules ./node_modules USER graphdone -EXPOSE 4000 3000 +EXPOSE 4127 -CMD ["node", "server/index.js"] \ No newline at end of file +WORKDIR /app/packages/server +CMD ["node", "dist/index.js"] 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..0a4060c7 100644 --- a/deployment/docker-compose.http.yml +++ b/deployment/docker-compose.http.yml @@ -42,6 +42,10 @@ services: 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: @@ -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..5abef897 100644 --- a/deployment/docker-compose.registry.yml +++ b/deployment/docker-compose.registry.yml @@ -44,12 +44,13 @@ services: 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 @@ -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..d82883e1 100644 --- a/deployment/docker-compose.yml +++ b/deployment/docker-compose.yml @@ -46,6 +46,10 @@ services: 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"] @@ -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..05603646 --- /dev/null +++ b/deployment/self-host/Dockerfile @@ -0,0 +1,23 @@ +FROM node:22-bookworm-slim 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 ./packages +# 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 +RUN npm run build -w @graphdone/core && npm run build -w @graphdone/server +RUN VITE_GRAPHQL_URL=/api/graphql npm run build -w @graphdone/web +FROM node:22-bookworm-slim AS api +WORKDIR /app +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/packages/core ./packages/core +COPY --from=build /app/packages/server ./packages/server +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..6cd2896d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,9 +21,13 @@ Welcome to the GraphDone documentation! This directory contains comprehensive gu ### [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** 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/` | 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: 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..842e40b4 100644 --- a/docs/USER_STORIES.md +++ b/docs/USER_STORIES.md @@ -121,3 +121,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 - -# 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://:3127` -- GraphQL API: `http://:4127/graphql` -- Neo4j Browser: `http://: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://: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= -``` - -## 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..4cb634f6 --- /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 node scripts/test-gateway-triage.mjs` — explicit one-call real gateway experiment, 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/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/README.md b/docs/deployment/README.md index 81350336..48254836 100644 --- a/docs/deployment/README.md +++ b/docs/deployment/README.md @@ -21,6 +21,41 @@ This guide covers various deployment options for GraphDone, from local developme ## 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 100% rename from DEVOPS_INTEGRATION.md rename to docs/deployment/devops-integration.md 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/detailed-overview.md b/docs/detailed-overview.md index 712d1b57..e65f2fea 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 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
React + TypeScript + D3.js
Touch-optimized UI
Port 3127] - IOS[iPhone App
SwiftUI
Separate GraphDone-iOS repo] + WEB[Web Application
React + TypeScript + D3.js
Responsive desktop + mobile web
Port 3127] CLAUDE[Claude Code MCP
Natural language interface
Port 3128] end @@ -25,7 +29,7 @@ graph TB subgraph "Data Layer" SQLITE[(SQLite
User authentication
User settings & preferences
Fast local storage)] - NEO4J[(Neo4j 5.15-community
Project management graph
Work items & dependencies
APOC plugins
Port 7687)] + NEO4J[(Neo4j 5.15-community
Management graph
Work items & dependencies
APOC plugins
Port 7687)] REDIS[(Redis 8-alpine
Available in Docker
Not yet integrated
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..e9417b0c 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 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/TAILSCALE_TROUBLESHOOTING.md b/docs/guides/tailscale-troubleshooting.md similarity index 100% rename from TAILSCALE_TROUBLESHOOTING.md rename to docs/guides/tailscale-troubleshooting.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 100% rename from SETUP_MULTIPASS.md rename to docs/guides/vm-setup.md 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
GraphProvider + ViewModeProvider"] + App --> Layout["Layout
sidebar · header · MobileBottomNav"] + Layout --> WS["Workspace
graph selector · data queries"] + WS --> VM["ViewManager
renders the active view mode"] + WS --> Insp["NodeInspector (docked)
+ on-canvas expand peek"] + + VM -->|list / cards| Card["CardView"] + VM -->|graph| SGV["SafeGraphVisualization
→ InteractiveGraphVisualization (D3)"] + VM -->|table · kanban · gantt
calendar · dashboard · activity| Other["other views"] + + VMC["ViewModeContext
active mode + persistence"] -.-> VM + VMC -.-> Layout + Insp --> Modes["Card · Contents (lazy markdown) · Diagram (sub-graph)"] + Audit["mobile-audit tests (CI)
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 (`` 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..37278c37 --- /dev/null +++ b/docs/quality/DURABILITY_REVIEW_2026-10-23.md @@ -0,0 +1,21 @@ +# 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. 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/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/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..82b466d5 --- /dev/null +++ b/docs/quality/RELEASE_GATES.md @@ -0,0 +1,54 @@ +# 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. + +## Current publication state + +Docker Build Validation builds the web, API and Neo4j images for amd64 and arm64 +without registry login, package-write permission, image pushes or stack manifests. +A container manifest cannot represent three different services as a deployable +stack. Image builds alone do not prove that those services work together. + +Deploy and Release are explicitly disabled. Their manual actions fail before +checkout, credentials, version edits, tags, releases or deployment. The previous +placeholder echo statements were not deployment implementations. A main merge +is not deployment evidence. These workflows do not deploy graphdone.com or move a release tag. + +Release implementation is tracked in [issue #190](https://github.com/GraphDone/GraphDone-Core/issues/190). +Before enabling publication in a separately reviewed change: + +1. Select and record a complete immutable source commit SHA. Check out that SHA + in every validation/build step; do not substitute the latest branch tip. +2. Require successful CI, Comprehensive Tests and the production-stack gate for + that exact source. Pending, missing, failed, cancelled, flaky or skipped + required evidence must prevent all registry/tag/release writes. +3. Keep PR execution read-only. Grant write permissions only to the authorized + release operation after validation. Test the event/permission conditions + without publishing before enabling writes. +4. Build using the lockfile and the intended Dockerfile, attach source and image + digest provenance, and prevent an older concurrent run from moving a newer + release tag. Version changes must be part of the validated tree. +5. Implement real environment-specific deployment and independently verify the + deployed version and application/database health. A successful build, push, + echo or worker-liveness endpoint is insufficient. + +## 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/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_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/SECURITY_SCOPE_2026-10-23.md b/docs/quality/SECURITY_SCOPE_2026-10-23.md new file mode 100644 index 00000000..07012d82 --- /dev/null +++ b/docs/quality/SECURITY_SCOPE_2026-10-23.md @@ -0,0 +1,50 @@ +# 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`. + +## 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. 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/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-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 +

All metrics

diff --git a/tests/generate-showcase-report.mjs b/tests/lib/reporting/generate-showcase-report.mjs similarity index 100% rename from tests/generate-showcase-report.mjs rename to tests/lib/reporting/generate-showcase-report.mjs diff --git a/tests/generate-vlm-report.mjs b/tests/lib/reporting/generate-vlm-report.mjs similarity index 100% rename from tests/generate-vlm-report.mjs rename to tests/lib/reporting/generate-vlm-report.mjs diff --git a/tests/lib/reporting/html.mjs b/tests/lib/reporting/html.mjs new file mode 100644 index 00000000..4e473a5a --- /dev/null +++ b/tests/lib/reporting/html.mjs @@ -0,0 +1,110 @@ +/** + * Pure HTML renderer for the unified report. Takes the aggregated report object + * (sequences may carry attachments {type:'image'|'video'|'file', name, href} whose href + * is already relative to the report dir) and returns a self-contained HTML string + * embedding screenshots and