diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 210bbdb8..954d5e16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,6 @@ on: push: branches: [main] pull_request: - branches: [main] permissions: contents: read @@ -19,11 +18,18 @@ env: jobs: build-and-test: runs-on: ubuntu-24.04 + timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false + - name: Verify exact checkout + env: + INKSPAN_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + actual_head="$(git rev-parse HEAD)" + test "$actual_head" = "$INKSPAN_EXPECTED_HEAD_SHA" # pnpm version comes from the package.json "packageManager" field # (pnpm 10+), which is required to read `overrides` from # pnpm-workspace.yaml consistently with the committed lockfile. @@ -47,7 +53,7 @@ jobs: browser-release-evidence: name: Cross-engine Clipboard / Playwright 1.62.0 runs-on: ubuntu-24.04 - timeout-minutes: 30 + timeout-minutes: 60 env: PLAYWRIGHT_BROWSERS_PATH: /tmp/inkspan-playwright-browsers steps: @@ -55,6 +61,12 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false + - name: Verify exact checkout + env: + INKSPAN_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + actual_head="$(git rev-parse HEAD)" + test "$actual_head" = "$INKSPAN_EXPECTED_HEAD_SHA" - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: @@ -74,6 +86,7 @@ jobs: office: name: Office / Python ${{ matrix.python-version }} runs-on: ubuntu-24.04 + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -88,6 +101,12 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false + - name: Verify exact checkout + env: + INKSPAN_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + actual_head="$(git rev-parse HEAD)" + test "$actual_head" = "$INKSPAN_EXPECTED_HEAD_SHA" - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.python-version }} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a42dd5a5..8d2d9ad0 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -43,7 +43,7 @@ flowchart TB Selector[Protected text-position-selector subpath] Autosave[Protected autosave subpath] Converter[Protected converter subpath] - Markdown[Proposed markdown subpath\nactive PR #114] + Markdown[Protected markdown subpath] SharedPolicy[Shared deterministic URI / inline-image policy] Serializers[Shared deterministic serializers] @@ -58,7 +58,7 @@ flowchart TB Markdown --> Serializers ``` -The active `@contextualwisdomlab/cwl-editor/markdown` work in PR #114 is governed by Proposed ADR 0020. Its purpose is dependency isolation, not a second serializer authority. Until that PR or a verified successor integrates, the proposed subpath is unshipped and the protected root package remains the public authority for those serializers. +Protected `main` includes the framework-independent `@contextualwisdomlab/cwl-editor/markdown` subpath under Accepted ADR 0020. It isolates dependency topology without creating a second serializer authority: the root package and the subpath share the same deterministic serializer and URI/inline-image policy modules. Packed ESM, CommonJS, and strict-TypeScript consumers are required evidence that the subpath remains browserless and independent of React, TipTap UI, Yjs, network, credential, and model authority. A framework-independent subpath must prove its declared dependency boundary from the packed npm artifact under ESM, CommonJS, and strict TypeScript consumers. Source-level import shape alone is insufficient release evidence. @@ -66,15 +66,15 @@ A framework-independent subpath must prove its declared dependency boundary from Inkspan owns the CSS rules it ships for its editor. It does not own the operating system print spooler, printer, browser's pagination implementation, downstream PDF storage, or host disclosure policy. -Protected `main` currently remains the stylesheet authority. Proposed ADR 0021 and active PR #116 define a CSS-only `@media print` boundary intended to: +Protected `main` includes the CSS-only `@media print` presentation boundary governed by Accepted ADR 0021. The shipped boundary: -- remove Inkspan-owned screen-only scroll/max-height clipping; -- hide toolbar, collaboration status, remote caret/cursor-label, and placeholder UI from printed document output; -- preserve authored document structures and links; -- use conservative paged-fragmentation hints; and -- keep links distinguishable without relying on color alone. +- removes Inkspan-owned screen-only scroll/max-height clipping; +- hides toolbar, collaboration status, remote caret/cursor-label, and placeholder UI from printed document output; +- preserves authored document structures and links; +- uses conservative paged-fragmentation hints; and +- keeps links distinguishable without relying on color alone. -This proposed presentation line does **not** create a JavaScript print mode, PDF service, page-number/header/footer authority, timestamp/signature claim, persistence layer, network requirement, credential, or model dependency. Until #116 integrates, the new print behavior is not shipped. +This protected presentation line does **not** create a JavaScript print mode, PDF service, page-number/header/footer authority, timestamp/signature claim, persistence layer, network requirement, credential, or model dependency. Browser print output remains a presentation representation; durable print/PDF authorization, storage, provenance, retention, and distribution remain host responsibilities. ## Modular MSA composition diff --git a/docs/DOCUMENTATION_FITNESS.md b/docs/DOCUMENTATION_FITNESS.md index cdb52e18..293b34be 100644 --- a/docs/DOCUMENTATION_FITNESS.md +++ b/docs/DOCUMENTATION_FITNESS.md @@ -68,6 +68,7 @@ Document fitness and implementation maturity are independent. A `present_current | TRACEABILITY | `docs/TRACEABILITY.md` | `present_current` | Protected requirements/standards/evidence mapping | Acquisition reviewers can distinguish protected evidence from active or planned work. | | Contributor/agent authority | `AGENTS.md`, `CLAUDE.md`, `docs/README.md` | `present_current` | Protected-main-first decision discipline | Agents are directed back to the same canonical graph rather than parallel private memory. | | Autonomous maintenance governance | `AGENTS.md`, `CLAUDE.md` plus the external scheduler | `present_current` | `out_of_scope` as Inkspan runtime behavior | Work-conserving execution, user-redirection incident recovery, lane-local waiting and external scheduler authority are reconstructable without pretending automation is an Inkspan API. | +| Product/technical gap baseline | `docs/product-technical-gap-baseline.md` | `present_current` | Dated operational snapshot; protected-main authority remains separate | Buyer-visible gaps, active PR lanes, release blockers, and the next executable loop are discoverable without promoting active-PR work to shipped behavior. | ## Conversation-to-GitHub reconciliation diff --git a/docs/README.md b/docs/README.md index 2b617f7c..001469fd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,7 @@ This directory is the discoverable index for Inkspan's product, technical, secur | [`../ARCHITECTURE.md`](../ARCHITECTURE.md) | Current protected-main implementation architecture and bounded-context ownership | | [`../SECURITY.md`](../SECURITY.md) | Protected-main private vulnerability reporting, supported security lines, coordinated disclosure, and claim limits | | [`DOCUMENTATION_FITNESS.md`](DOCUMENTATION_FITNESS.md) | Acquisition completeness matrix, implementation maturity, deliberate non-applicability and remaining canonical gaps | +| [`product-technical-gap-baseline.md`](product-technical-gap-baseline.md) | Dated protected-main product/technical gap register, live PR lanes, release blockers, and executable maintenance loop | | [`assessments/2026-08-10-conversation-documentation-reassessment.md`](assessments/2026-08-10-conversation-documentation-reassessment.md) | Dated whole-conversation reassessment, protected/operational deltas, and scheduler-control remediation | | [`PRD.md`](PRD.md) | Product users, jobs, buyer outcomes, non-goals, acceptance and claim boundaries | | [`TRD.md`](TRD.md) | Technical invariants, runtime boundaries, failure semantics and release evidence | diff --git a/docs/doctoring/editor-product-completion-research.md b/docs/doctoring/editor-product-completion-research.md new file mode 100644 index 00000000..6c0f4cb4 --- /dev/null +++ b/docs/doctoring/editor-product-completion-research.md @@ -0,0 +1,238 @@ +# Editor product-completion research + +Status: Proposed doctoring evidence — 2026-08-20 +Protected implementation baseline: `main@3b38ead2d00f44eb578d0689087b9293b3dabe1e` +Active planning writer: PR [#372](https://github.com/ContextualWisdomLab/inkspan/pull/372) + +## Purpose + +This record compares Inkspan's protected product promise, published package +surface, tests, executable examples, current pull-request queue, and buyer +workflows against current standards and public product evidence. It identifies +which missing capabilities are credible product gaps and which apparent gaps +would violate Inkspan's existing authority boundary. + +This document is planning and traceability evidence. It does not prove that any +new capability is implemented, protected, published, certified, or supported. +Protected `main` remains the only implementation authority. Active pull requests +and issues remain proposals until they integrate through current governance and +the resulting package artifacts are verified. + +## Evidence hierarchy and method + +The research used this evidence order: + +1. protected Inkspan source, public package metadata, canonical documents, and + executable tests; +2. exact current pull-request metadata, changed paths, reviews, and workflow + results; +3. W3C Recommendations and explicitly identified W3C Working Drafts; +4. public vendor documentation as evidence of buyer workflows and market + expectations, never as proof of Inkspan behavior or performance. + +Vendor claims are not imported as Inkspan acceptance thresholds. Inkspan must +establish its own support envelope from reproducible exact-head measurements. +A Working Draft is cited as work in progress rather than represented as a +certification target. + +## Protected-product observations + +### Review evidence exists, but a review workflow does not + +The protected PRD names authors and reviewers and already scopes selection and +annotation evidence to exact canonical document revisions. Inkspan also exposes +a W3C `TextPositionSelector` projection. The host deliberately owns durable +annotation identifiers, comment bodies, authentication, authorization, +persistence, moderation, notification, retention, and cross-revision +re-anchoring. + +That boundary is sound, but the protected public package has no provider-neutral +review subpath, comment/suggestion presentation surface, deterministic +accept/reject transaction, or executable revision-comparison journey. Buyers +must currently build the complete reviewer interaction layer themselves. + +The W3C Web Annotation Data Model provides an interoperable model for an +Annotation, Body, Target, selector, and state without prescribing a transport or +persistence service. That is compatible with an Inkspan-owned validation and UI +surface plus host-owned durable storage and policy. Public Tiptap and CKEditor +documentation also treats comments, tracked changes, and revision history as +coherent review workflows. These vendor references establish market relevance; +they do not define Inkspan's implementation. + +Result: issue [#374](https://github.com/ContextualWisdomLab/inkspan/issues/374) +owns the proposed provider-neutral comments, suggestions, and revision-review +vertical. + +### Correctness gates exist, but a supported performance envelope does not + +Protected Inkspan has extensive deterministic correctness, browser, package, +Office, security, and coverage evidence. It does not have one canonical +large-document corpus or an accepted latency, throughput, peak-memory, leak, or +gracious-rejection support contract for editor mount, input, serialization, +revision evidence, autosave, collaboration updates, or Office conversion. + +The W3C Event Timing API defines browser-observable interaction latency from +trusted input through the following paint. It is a current Working Draft and is +useful as a measurement mechanism, not as a certification. Public Tiptap +conversion release notes show that large-document memory and latency are buyer +and retention concerns, but their vendor measurements cannot become Inkspan +claims. + +Result: issue [#375](https://github.com/ContextualWisdomLab/inkspan/issues/375) +owns a deterministic benchmark corpus, support envelope, regression rule, and +root-cause optimization lane. + +### Multilingual typography exists, but CJK input support is not proven + +Inkspan bundles offline Korean, Japanese, Simplified and Traditional Chinese, +Vietnamese, and Latin fonts. Protected browser evidence covers important desktop +boundaries, but repository searches found no explicit composition-event +contract, CJK IME regression corpus, mobile/touch acceptance lane, or real-device +support record. + +Typography coverage is not input-method correctness. Rich-text transactions, +controlled value updates, autosave, Yjs updates, toolbar actions, and undo/redo +can interact with an active composition session. A product can render Korean or +Japanese correctly while still losing, duplicating, reordering, or prematurely +persisting composed text. + +Input Events Level 2 defines the evolving `beforeinput`, `input`, and composition +semantics and is a Working Draft. Pointer Events Level 3 is a W3C +Recommendation for hardware-agnostic pointer input. WCAG 2.2 Success Criterion +2.5.8 provides the applicable 24-by-24 CSS pixel target-size or spacing +requirements and defined exceptions for touch-oriented controls. + +Result: issue [#376](https://github.com/ContextualWisdomLab/inkspan/issues/376) +owns the cross-engine CJK IME, touch, mobile, and truthful support-matrix lane. + +### Package contracts are rich, but the executable host example is too narrow + +The current `demo/App.tsx` imports `../src/index.js` and demonstrates controlled +Markdown/HTML editing and image rejection. It does not install the packed +artifact or exercise the integration paths most likely to fail in production: +SSR and hydration, native forms, strong-validator autosave, conflicts, +host-owned Yjs provider lifecycle, read-only transitions, delayed model +proposals, reconnect and teardown, package CSS/fonts, and Office/converter +handoff. + +Inkspan's existing host/editor authority split should not be widened merely to +make a demo convenient. A reference application can prove the boundary while +using replaceable synthetic adapters and explicitly refusing production claims +for identity, persistence, collaboration authorization, or model access. + +Result: issue [#377](https://github.com/ContextualWisdomLab/inkspan/issues/377) +owns an executable packed-package reference host and acquisition-readable +integration evidence. + +## Gap and issue traceability + +| Priority | Buyer problem | Protected observation | Owning issue | Smallest credible result | +| --- | --- | --- | --- | --- | +| P0 | A verified installable stable package is unavailable through the promised release path | Source versions are `0.6.0`, while stable publication and public digest/provenance acceptance remain open | [#118](https://github.com/ContextualWisdomLab/inkspan/issues/118) plus its existing PR lanes | Exact protected release, registry publication through supported identity, public digest and provenance verification | +| P0 | Current shipped accessibility and dependency findings remain unresolved on protected `main` | Repairs exist on active PRs, not protected implementation | [#362](https://github.com/ContextualWisdomLab/inkspan/pull/362), [#373](https://github.com/ContextualWisdomLab/inkspan/pull/373) | Qualifying review, exact-head checks, protected integration, regenerated release evidence | +| P1 | Reviewers cannot complete comments and suggestion acceptance through a reusable Inkspan surface | Revision/selector evidence exists; review workflow does not | [#374](https://github.com/ContextualWisdomLab/inkspan/issues/374) | React-free review contract plus accessible host-controlled comment/suggestion UI and deterministic accept/reject | +| P1 | Buyers cannot size or procure Inkspan for large documents | No published or gated support envelope | [#375](https://github.com/ContextualWisdomLab/inkspan/issues/375) | Synthetic benchmark corpus, exact-head budgets, graceful rejection, scheduled regression evidence | +| P1 | CJK and mobile authoring claims exceed current input evidence | Fonts exist; composition/mobile assurance does not | [#376](https://github.com/ContextualWisdomLab/inkspan/issues/376) | Cross-engine composition tests, periodic real-device evidence, touch accessibility, truthful support matrix | +| P1 | Buyers must infer integration correctness from prose and a source-relative demo | Demo does not install the packed artifact or exercise host lifecycle conflicts | [#377](https://github.com/ContextualWisdomLab/inkspan/issues/377) | Packed-package Next.js reference host with SSR, autosave, conflict, Yjs lifecycle, forms, and stale proposal paths | +| P2 | Broader Office authoring/import workflows are not protected | Existing Draft PRs already own DOCX, HWP/HWPX, and spreadsheet imports | [#323](https://github.com/ContextualWisdomLab/inkspan/pull/323), [#320](https://github.com/ContextualWisdomLab/inkspan/pull/320), [#318](https://github.com/ContextualWisdomLab/inkspan/pull/318) | Integrate one bounded format at a time with realistic fixtures and exact package evidence | +| P2 | Host-supplied writing diagnostics are not shipped | Existing stacked PRs already own the bounded diagnostics architecture and implementation | [#248](https://github.com/ContextualWisdomLab/inkspan/pull/248) and its stack | Resolve stack evidence and integrate dependency-first without adding model/provider authority | + +## Design and standards interpretation + +### Web Annotation is an interchange model, not an Inkspan database mandate + +Inkspan can project review targets through W3C annotation selectors while the +host retains bodies, identities, authorization, persistence, legal hold, and +notification. A review package should therefore validate and render bounded +host-supplied data rather than creating hidden application authority. + +### Input Events is an evolving interoperability reference + +The composition lifecycle from Input Events Level 2 should guide tests and +contract wording. Because the document is a Working Draft, Inkspan must pin the +browser behavior it actually verifies and publish known divergences. It must not +claim standards certification merely because event names match a draft. + +### Pointer and accessibility standards constrain mobile UI + +Pointer Events supports mouse, touch, and pen through one event model. WCAG 2.2 +SC 2.5.8 constrains the size or spacing of Inkspan-owned interactive targets. +These standards do not prove that a desktop emulation reproduces a real mobile +IME; real-device evidence remains a separate support requirement. + +### Event Timing supplies measurements, not favorable budgets + +Event Timing can collect trusted input latency. Initial Inkspan benchmarks must +determine the supported document profiles and budgets. A later regression may +not be hidden by silently increasing a threshold; a deliberate budget change +requires evidence and buyer-impact review. + +## Deliberate non-gaps and exclusions + +The research does not recommend the following as product-completion work: + +- an Inkspan-owned application database, identity provider, tenant directory, + notification service, or durable annotation store; +- an embedded collaboration credential, room-authorization service, or + provider-specific runtime; +- an embedded LLM provider, API key, model router, prompt store, or semantic + fallback for writing diagnostics; +- a new server-side PDF service merely because print output exists. Accepted + ADR 0021 deliberately limits protected Inkspan to its shipped CSS paged-media + boundary; durable PDF authority requires a separate buyer requirement and a + superseding ADR; +- duplicate DOCX, HWP/HWPX, spreadsheet, diagnostics, release, or stacked-CI + Issues while existing active owners remain open; +- a Figma File ID that does not correspond to an actual Figma artifact. If + accepted UI design work uses Figma, the accepting ADR must record the real + File ID before calling that design contract complete; +- Rust, GPU, or a new compute service without profiling evidence for an + Inkspan-owned workload, an architecture decision, portability analysis, and + parity tests. + +## Canonical-document impact of future implementation + +Every issue above must update only the canonical records its durable contract +changes: + +- #374: PRD, TRD, contracts, architecture, data model, threat model, + accessibility, package distribution, UML, Storybook inventory, and a new ADR; +- #375: operability, test strategy, performance evidence, support matrix, + release acceptance, and an ADR defining budgets and change control; +- #376: accessibility, browser/device support, collaboration/autosave behavior, + test strategy, Storybook responsive states, and an input-lifecycle ADR; +- #377: package distribution, integration guide, architecture ownership diagram, + operability, test strategy, and the executable example itself. + +No issue may mark a protected capability complete merely because its design +record or local tests exist. Protected integration, exact-head governance, and +published artifact evidence remain separate gates. + +## References — APA 7th + +CKEditor. (n.d.). *Revision history*. Retrieved August 20, 2026, from +https://ckeditor.com/docs/ckeditor5/latest/features/collaboration/revision-history/revision-history.html + +CKEditor. (n.d.). *Track changes*. Retrieved August 20, 2026, from +https://ckeditor.com/docs/ckeditor5/latest/features/collaboration/track-changes/track-changes.html + +Isik, P. (2025, June 24). *Faster, lighter, and more reliable DOCX import/export +with Tiptap*. Tiptap. https://tiptap.dev/blog/release-notes/faster-lighter-and-more-reliable-docx-import-export-with-tiptap + +Tiptap. (n.d.). *Tracked changes with comments*. Retrieved August 20, 2026, from +https://tiptap.dev/docs/tracked-changes/guides/comments-integration + +World Wide Web Consortium. (2017). *Web Annotation Data Model*. +https://www.w3.org/TR/annotation-model/ + +World Wide Web Consortium. (2024). *Web Content Accessibility Guidelines +(WCAG) 2.2*. https://www.w3.org/TR/WCAG22/ + +World Wide Web Consortium. (2026, March 19). *Event Timing API* (Working Draft). +https://www.w3.org/TR/event-timing/ + +World Wide Web Consortium. (2026, May 1). *Input Events Level 2* (Working Draft). +https://www.w3.org/TR/input-events-2/ + +World Wide Web Consortium. (2026, June 30). *Pointer Events Level 3*. +https://www.w3.org/TR/pointerevents3/ diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md new file mode 100644 index 00000000..6dbd7a47 --- /dev/null +++ b/docs/product-technical-gap-baseline.md @@ -0,0 +1,229 @@ +# Inkspan product-technical gap baseline + +Status: Dated operational baseline — 2026-08-21; live snapshot refetched after `2026-08-21T03:29:21Z` + +This record turns the current protected-main product boundary, live pull-request +queue, release evidence, and buyer-visible gaps into an executable maintenance +queue. It is a snapshot, not a substitute for refetching GitHub state before a +merge, release, or governance decision. Protected `main` remains the +implementation authority; an active PR or Issue is not a shipped capability. + +The research basis for the buyer-gap additions is +[`docs/doctoring/editor-product-completion-research.md`](doctoring/editor-product-completion-research.md). +It distinguishes protected evidence, active-PR evidence, standards, and vendor +market evidence and includes APA 7th references. + +## Source of truth and observed baseline + +| Fact | Current evidence | Meaning | +| --- | --- | --- | +| Protected source | `main@3b38ead2d00f44eb578d0689087b9293b3dabe1e` | The only source head used for shipped-product claims. | +| Repository state | `ContextualWisdomLab/inkspan`, default branch `main` | This is the correct repository for Inkspan-owned editor, conversion, evidence, accessibility, package, and provider-neutral adapter work. | +| Open PR queue | Complete bounded inventory: 64 open PRs, 3 Ready and 61 Draft; 10 target non-main stack bases | The queue is active work, not protected implementation. Refetch immediately before lifecycle action. | +| Current source versions | npm `0.6.0`; Office manifest `0.6.0` | Version alignment exists in source, but it does not prove registry publication. | +| Public release evidence | The last observed GitHub release was `v0.3.1`; no protected `v0.6.0` publication/digest acceptance was established; the live npm probe returned registry `E404` and the PyPI probe returned no matching `inkspan-office` distribution | Stable `0.6.0` publication remains an operational gap until freshly reverified. | +| Protected governance | Active central required-workflow policy and review requirements were observed in the release baseline | No self-approval, predecessor-evidence transfer, or governance bypass is valid. Live rules and permissions must be refetched. | +| Main checks | The current exact protected-main `build-and-test`, Office Python 3.11, and Chromium/Firefox/WebKit checks are terminal success | Protected-main checks do not transfer to active PR heads or prove registry publication. | +| Protected-main dependency alerts | Five Dependabot alerts are open across `brace-expansion`, `postcss`, and `fast-uri`; PR #373 targets the relevant patched floors and the audited `nanoid` tree | The alert state is not closed until the exact PR is reviewed, integrated, and rechecked on protected `main`. | + +The queue, release, registry, alert, and governance values are mutable. Refresh +with bounded queries instead of copying this snapshot into a lifecycle decision: + +```bash +set -euo pipefail +gh api --paginate \ + 'repos/ContextualWisdomLab/inkspan/pulls?state=open&per_page=100' \ + --jq '.[] | [.number,.title,.draft,.head.ref,.base.ref,.updated_at] | @tsv' +# Open issues only: is:issue excludes pull requests, so the issue queue and the +# open-PR queue never double-count one another. +gh api --paginate \ + 'search/issues?q=repo%3AContextualWisdomLab%2Finkspan+is%3Aissue+state%3Aopen&per_page=100' \ + --jq '.items[] | [.number,.title,.updated_at] | @tsv' +gh api --paginate \ + 'repos/ContextualWisdomLab/inkspan/pulls?state=open&per_page=100' \ + --jq '[.[] | {draft}] | {open:length,ready:(map(select(.draft == false)) | length),draft:(map(select(.draft == true)) | length)}' +gh api repos/ContextualWisdomLab/inkspan/branches/main --jq '.commit.sha' +gh release list --repo ContextualWisdomLab/inkspan --limit 5 +if registry_npm_output="$(npm view @contextualwisdomlab/cwl-editor version 2>&1)"; then + printf '%s\n' "$registry_npm_output" +elif printf '%s\n' "$registry_npm_output" | grep -q 'E404\|404 Not Found'; then + printf '%s\n' "$registry_npm_output" +else + printf '%s\n' "$registry_npm_output" >&2 + exit 1 +fi +if registry_pypi_output="$(python3 -I -m pip index versions inkspan-office --index-url https://pypi.org/simple --disable-pip-version-check 2>&1)"; then + printf '%s\n' "$registry_pypi_output" +elif printf '%s\n' "$registry_pypi_output" | grep -q 'No matching distribution'; then + printf '%s\n' "$registry_pypi_output" +else + printf '%s\n' "$registry_pypi_output" >&2 + exit 1 +fi +gh api repos/ContextualWisdomLab/inkspan/dependabot/alerts \ + --jq '.[] | select(.state == "open") | [.number,.dependency.package.name,.security_advisory.severity,.security_vulnerability.first_patched_version.identifier] | @tsv' + +candidate_prs=(248 249 251 254 256 257 266 270 277 279 280 281 282 285 290 292 295 299 318 320 323 351 354 359 362 372 378 379 380 381) +for pr_number in "${candidate_prs[@]}"; do + pr_json="$(gh api "repos/ContextualWisdomLab/inkspan/pulls/$pr_number")" + head_sha="$(printf '%s\n' "$pr_json" | jq -r '.head.sha')" + printf '%s\n' "PR #$pr_number head=$(printf '%s\n' "$pr_json" | jq -r '.head.sha') base=$(printf '%s\n' "$pr_json" | jq -r '.base.sha')" + gh api --paginate "repos/ContextualWisdomLab/inkspan/pulls/$pr_number/files?per_page=100" \ + --jq '.[].filename' + gh api --paginate "repos/ContextualWisdomLab/inkspan/pulls/$pr_number/reviews?per_page=100" \ + --jq '.[] | [.state,.user.login,.submitted_at] | @tsv' + gh api --paginate "repos/ContextualWisdomLab/inkspan/commits/$head_sha/check-runs?per_page=100" \ + --jq '.check_runs[] | [.name,.status,.conclusion] | @tsv' + gh api graphql \ + -f query='query($owner:String!, $name:String!, $number:Int!, $endCursor:String) { repository(owner:$owner, name:$name) { pullRequest(number:$number) { reviewThreads(first:100, after:$endCursor) { pageInfo { hasNextPage endCursor } nodes { isResolved path line } } } } }' \ + -F owner=ContextualWisdomLab -F name=inkspan -F number="$pr_number" \ + --paginate \ + --jq '.data.repository.pullRequest.reviewThreads.nodes[] | [.isResolved,.path,.line] | @tsv' +done +gh api repos/ContextualWisdomLab/inkspan/rulesets --paginate \ + --jq '.[] | [.id,.name,.enforcement,.target] | @tsv' +``` + +For every candidate PR, independently refetch its exact head/base, changed +paths, formal reviews, unresolved threads, current-head workflows, and then-live +organization rules. A PR body is not authority for those mutable facts. + +## Product and ownership boundary + +Inkspan currently owns deterministic Markdown/HTML authoring, bounded document +and evidence contracts, local autosave coordination, accessibility metadata, +safe conversion, the standalone package boundary, and provider-neutral +adapters. The embedding host owns authenticated transport, authentication and +authorization, tenant isolation, durable persistence, credentials, migrations, +retention, deployment, durable audit, collaboration-provider authority, and +model-use policy. This boundary is implemented in the canonical PRD, TRD, +contracts, architecture, threat model, and data model. + +The following are deliberate non-gaps, not reasons to add speculative runtime +surface: + +- Inkspan owns no application database, so a physical ERD, database migrations, + third-normal-form tables, and hot-partition policy remain host deliverables. + If persistence authority moves into Inkspan, add a versioned ADR, logical and + physical ERD, migration/rollback evidence, and a partitioning test before + implementation. +- Inkspan is not a psychometrics, mathematical-science, or model-orchestration + runtime. Rust/GPU execution, multi-level or longitudinal estimation, and + contextual-orchestrator model routing are not valid gap closures for this + product boundary. Add them only after a buyer requirement and a measured + Inkspan-owned computation boundary exist. +- PII handling remains a host-governed privacy and authorization decision. + Moving credentials, tenant identifiers, authored content, comment bodies, or + model prompts into public editor evidence would violate the existing security + boundary rather than close a product gap. +- Accepted ADR 0021 deliberately limits protected Inkspan to its CSS paged-media + and print-fidelity boundary. A durable PDF service is not a gap unless a + separate buyer requirement and superseding ADR establish that authority. +- Existing active owners already cover DOCX, HWP/HWPX, spreadsheet import, + writing diagnostics, release workflow, and stacked-PR CI. Duplicate Issues or + PRs for those source boundaries would increase conflict rather than completion. + +## Current PR and release lanes + +| Lane | Current authority | Buyer impact | Gate / next action | +| --- | --- | --- | --- | +| [PR #362](https://github.com/ContextualWisdomLab/inkspan/pull/362) — editor contrast and keyboard focus | Ready active PR; exact head `11d5cfecdcc0949ec98e6ca110d482124bff00c4` at the latest refetch | Protected dark active-toolbar text is below the intended WCAG normal-text target, and the editable surface lacks a replacement focus indicator. | Current exact-head checks have 22 successful completions and a failed `strix` gate. Qualifying independent approval and applicable formal/central review evidence also remain absent. Do not dismiss the security finding, duplicate review requests, or self-approve. | +| [PR #373](https://github.com/ContextualWisdomLab/inkspan/pull/373) — patched transitive dependency floors | Ready active PR; exact head `41f978853629c3bed8ef5393f685053b24322490` at the latest refetch | Buyers need a clean, reproducible dependency audit without moving transport, credential, or runtime authority into Inkspan. | Current exact-head checks have 22 successful completions and `opencode-review` pending, with no completed failure observed in the check-run set. No qualifying current-head approval was observed; queued evidence is not passing evidence. Refetch all then-live central/review gates before integration. | +| [PR #156](https://github.com/ContextualWisdomLab/inkspan/pull/156) — protected documentation maturity | Draft canonical documentation owner; exact head `ab1cc11ddf9148b9db668918fd6057e0b444acc3` at the latest refetch | Buyers and acquirers need shipped capability records to distinguish protected implementation from historical or active-PR claims. | The exact head contains the ownership-safe maturity tests and no release-inventory writer; focused local evidence is 16/16, while hosted repository workflows are newly queued. Release-inventory corrections remain solely with #362. Do not transfer predecessor evidence or self-approve. | +| [PR #372](https://github.com/ContextualWisdomLab/inkspan/pull/372) — product-technical gap baseline | Ready active documentation PR | Buyers and maintainers need one evidence-backed view of shipped boundaries, release readiness, and the next credible product closures. | This document is its own writer, so an embedded current-head SHA or workflow run would become stale on every update. Evaluate only the live PR head returned by GitHub after the final commit, then require exact-head checks, formal review, and live governance. Predecessor #372 checks do not transfer after this update. | +| [Issue #118](https://github.com/ContextualWisdomLab/inkspan/issues/118) — stable release acceptance | Open release issue | Buyers cannot install and verify the protected `0.6.0` artifact through the promised release path. | Integrate the release-blocking product and workflow owners in dependency order, regenerate exact protected evidence, create the supported release identity, and verify public npm/PyPI bytes and provenance. | +| [PR #285](https://github.com/ContextualWisdomLab/inkspan/pull/285) — hostile-input/browser/release assurance | Draft, stacked on writing diagnostics | Release browser evidence has a known finite-time admission defect and the branch proposes a package-specific SBOM inventory. | Do not duplicate `.github/workflows/release.yml` ownership. Advance its stack only through exact-head evidence; treat its proposed inventory as non-authoritative until protected. | +| [PR #299](https://github.com/ContextualWisdomLab/inkspan/pull/299) — exact-head gates for stacked PRs | Draft, targets protected `main` | Stacked work can otherwise receive no repository PR generation or evidence that proves the contributor head. | Resolve its current-head workflow and review gates, then require each child stack head to obtain its own exact evidence. | +| [PR #323](https://github.com/ContextualWisdomLab/inkspan/pull/323), [#320](https://github.com/ContextualWisdomLab/inkspan/pull/320), [#318](https://github.com/ContextualWisdomLab/inkspan/pull/318) — Office imports | Draft feature lanes | Word, HWP/HWPX, and spreadsheet import broaden buyer workflows but are not shipped. | Keep one format/source owner at a time; require bounded deterministic conversion, realistic fixtures, privacy-safe diagnostics, and exact packed-package evidence before Ready. | +| [PR #378](https://github.com/ContextualWisdomLab/inkspan/pull/378) / [#382](https://github.com/ContextualWisdomLab/inkspan/pull/382) — review contract and controlled UI | Draft owners for Issue #374; latest observed heads `670356c80820ddedce22cabd22332bab22524045` and `b121431e00cd429e02cacdd0a966135c11451faa` | Buyers need provider-neutral review targets/proposals and an accessible host-controlled presentation without moving annotation identity, persistence, or model authority into Inkspan. | Keep the React-free package contract and controlled UI as separate owned lanes; require exact-head package, accessibility, browser, review, and governance evidence before lifecycle changes. | +| [PR #379](https://github.com/ContextualWisdomLab/inkspan/pull/379), [#380](https://github.com/ContextualWisdomLab/inkspan/pull/380), [#381](https://github.com/ContextualWisdomLab/inkspan/pull/381) — performance, CJK/mobile, and reference host | Draft owners for Issues #375–#377; latest observed heads `a838dbefb3a410d9899985ae1f011e71af3a9c3d`, `c56ce124ea8522e442a9251986c4ffe8028712b8`, and `4b211fedd5086a749d03a0413604f94819903a95` | Buyers need measurable large-document behavior, truthful multilingual/mobile input assurance, and a packed-artifact integration path that preserves host ownership. | Keep each evidence lane independent; do not infer support, mobile, or production integration from synthetic fixtures or a Draft branch. Require the acceptance matrices and exact-head checks named by each Issue. | +| [Issues #374](https://github.com/ContextualWisdomLab/inkspan/issues/374), [#375](https://github.com/ContextualWisdomLab/inkspan/issues/375), [#376](https://github.com/ContextualWisdomLab/inkspan/issues/376), [#377](https://github.com/ContextualWisdomLab/inkspan/issues/377) — new buyer completion verticals | Open Issues; no implementation PR is claimed | Review workflow, large-document support, CJK/mobile input assurance, and a reference host are missing from protected product evidence. | Execute only after confirming no current source owner conflicts. Each Issue defines its ownership boundary, acceptance evidence, and relationship to the P0 release lane. This documentation PR does not close them. | + +The remaining open PRs are mostly independent reliability, security, +accessibility, documentation, import, and diagnostics lanes. They must be +processed by existing source ownership and dependency order. A Draft label is +not a reason to merge, and a blocked lane is not a reason to stop unrelated safe +work. + +## Research-backed prioritization + +The detailed comparison and APA 7th references are in +[`docs/doctoring/editor-product-completion-research.md`](doctoring/editor-product-completion-research.md). +The resulting order is: + +1. **P0 — make the current product safely installable:** #118, #362, #373, the + existing release-workflow owner, and exact-head stacked-PR governance. +2. **P1 — complete the buyer workflow:** #374 review/comments/suggestions, + #375 performance envelope, #376 CJK/IME/mobile assurance, and #377 an + executable packed-package reference host. +3. **P2 — broaden formats and optional product surfaces:** existing Office + imports and the existing writing-diagnostics stack, dependency-first and + without duplicating authority. + +The P1 Issues are deliberately separate because they can be reviewed and +measured independently. They should stack only where a real public contract or +source dependency requires it. + +## Gap register + +| ID | Buyer-visible gap | Protected-main status | Smallest credible closure | +| --- | --- | --- | --- | +| G-01 | Default editor accessibility release blocker | `planned` / active PR #362 | Obtain fresh same-head governance evidence, an eligible approval, and terminal required workflows; integrate only the current head and regenerate protected release evidence. | +| G-02 | Stable registry publication and public artifact verification | `planned`, tracked by #118 | Use the protected release workflow and OIDC Trusted Publishing; verify exact npm/PyPI bytes and provenance after publication. | +| G-03 | Release browser admission reliability | `implemented_on_active_pr` in #285 | Keep browser evidence mandatory while increasing only the bounded job ceiling through its owning stack; verify the exact packed artifact across Chromium, Firefox, and WebKit. | +| G-04 | Stacked-PR exact-head governance | `implemented_on_active_pr` in #299 | Make workflow dispatch and checks prove the contributor head/base pair, then refetch current runs and review evidence for each child. | +| G-05 | Design-system discovery for repeating editor chrome | `implemented_on_active_pr` in #362 / ADR 0031 | Integrate the typed token catalog and Storybook inventory only after accessibility, package, and documentation checks pass. Protected main currently has no Storybook inventory. | +| G-06 | Broader deterministic Office import | `planned` / Draft PRs #323, #320, #318 | Close one bounded format contract at a time with realistic fixtures, fail-closed limits, package consumers, and supported-runtime evidence. | +| G-07 | Acquisition evidence freshness | `partial` | Refresh this dated baseline and canonical doctoring after every protected merge, release decision, or external control-plane change; never encode mutable run IDs as timeless architecture. | +| G-08 | Patched transitive development-tool dependency floor | `implemented_on_active_pr` in #373 | Preserve the narrow overrides and realpath containment fix, obtain qualifying review/live gates, integrate, then verify alerts are closed on protected main. | +| G-09 | Provider-neutral review, comments, suggestions, and revision comparison | `missing`, tracked by [#374](https://github.com/ContextualWisdomLab/inkspan/issues/374) | Add a React-free bounded review contract plus accessible host-controlled presentation and deterministic revision-bound accept/reject; do not acquire annotation persistence or identity authority. | +| G-10 | Large-document latency, memory, and graceful-rejection support envelope | `missing`, tracked by [#375](https://github.com/ContextualWisdomLab/inkspan/issues/375) | Build deterministic multilingual/Office fixtures, measure trusted operations, accept budgets by ADR, and gate material regressions without exposing document content. | +| G-11 | CJK IME, touch, and mobile editing assurance | `missing`, tracked by [#376](https://github.com/ContextualWisdomLab/inkspan/issues/376) | Specify composition semantics; test Korean/Japanese/Chinese/Vietnamese and collaboration/autosave interactions across engines; add periodic real-device evidence and a truthful support matrix. | +| G-12 | Executable production-shaped reference integration | `missing`, tracked by [#377](https://github.com/ContextualWisdomLab/inkspan/issues/377) | Install the packed artifact into a Next.js reference host and prove SSR/hydration, native forms, strong-validator autosave, conflict recovery, Yjs lifecycle, stale proposals, CSS/fonts, and teardown without claiming host services. | + +## Figma and Storybook boundary + +The active design-token ADR rejects Figma Variables synchronization as Inkspan +runtime or credential authority and keeps CSS as the runtime source of truth. +This baseline therefore does not invent a Figma file or claim a Figma +integration. If an accepted buyer-facing design artifact becomes required, use +Figma for that design work and record the actual Figma File ID in the accepting +ADR before calling the design contract complete. + +Storybook remains a local component-preview and executable-evidence surface, not +a production transport or source of document authority. The review, mobile, and +reference-host Issues require Storybook or equivalent executable states only +where those states improve reviewability and accessibility evidence. + +## Autonomous execution loop + +At each external scheduler interval: + +1. Refetch protected `main`, the full open-PR/Issue queue, exact heads and bases, + changed paths, reviews, unresolved threads, required workflows, security + findings, release/tag/registry state, documentation fitness, and buyer gaps. +2. For each actionable PR, inspect current findings, apply the smallest + root-cause fix in its owned branch, rerun applicable checks, and merge only + after current governance authorizes it. Keep review latency and queued checks + local to that lane. +3. If the queue has no actionable PR, execute the highest-value unclaimed Gap + above with a real test/runtime proof and the smallest canonical-document + update required by the durable contract. +4. Run two fresh whole-repository sweeps before stopping. A newly executable + item resets the sweep count. A prompt update, status report, documentation + assessment, or queued check is intermediate work, not completion. + +This loop is control-plane governance, not an Inkspan runtime feature. The +external scheduler owns cadence; Inkspan owns only the product and evidence +boundaries documented in the canonical graph. + +## Verification limits + +This snapshot supports queue selection on 2026-08-21 only. Before any merge or +release, re-read `docs/README.md`, `docs/PRD.md`, `docs/TRD.md`, +`docs/CONTRACTS.md`, `ARCHITECTURE.md`, relevant ADRs, current source/tests, the +research doctoring record, exact GitHub heads/checks/reviews, and live +governance. Cite new standards and research in the existing APA 7th doctoring +and traceability surfaces. Never use a model response, PR prose, predecessor +checks, a queued workflow, or local green tests as a substitute for exact +protected-main proof. diff --git a/src/architectureProtectedMainTruth.test.ts b/src/architectureProtectedMainTruth.test.ts new file mode 100644 index 00000000..c69b1725 --- /dev/null +++ b/src/architectureProtectedMainTruth.test.ts @@ -0,0 +1,30 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +const architecture = readFileSync( + resolve(process.cwd(), 'ARCHITECTURE.md'), + 'utf8', +); + +describe('protected-main architecture truth', () => { + it('does not describe merged Markdown or print capabilities as active proposals', () => { + expect(architecture).toContain( + 'Protected `main` includes the framework-independent `@contextualwisdomlab/cwl-editor/markdown` subpath', + ); + expect(architecture).toContain( + 'Protected `main` includes the CSS-only `@media print` presentation boundary', + ); + expect(architecture).not.toContain( + 'Proposed markdown subpath\\nactive PR #114', + ); + expect(architecture).not.toContain( + 'The active `@contextualwisdomlab/cwl-editor/markdown` work in PR #114', + ); + expect(architecture).not.toContain( + 'Proposed ADR 0021 and active PR #116 define a CSS-only `@media print` boundary', + ); + expect(architecture).not.toContain('Until #116 integrates'); + }); +}); diff --git a/src/canonicalProductDocumentation.test.ts b/src/canonicalProductDocumentation.test.ts index 746ed52e..af441858 100644 --- a/src/canonicalProductDocumentation.test.ts +++ b/src/canonicalProductDocumentation.test.ts @@ -14,6 +14,7 @@ const requiredFiles = [ 'SECURITY.md', 'docs/README.md', 'docs/DOCUMENTATION_FITNESS.md', + 'docs/product-technical-gap-baseline.md', 'docs/PRD.md', 'docs/TRD.md', 'docs/CONTRACTS.md', @@ -77,6 +78,7 @@ describe('canonical product documentation graph', () => { '../ARCHITECTURE.md', '../SECURITY.md', 'DOCUMENTATION_FITNESS.md', + 'product-technical-gap-baseline.md', 'PRD.md', 'TRD.md', 'CONTRACTS.md', @@ -260,6 +262,7 @@ describe('canonical product documentation graph', () => { 'TEST_STRATEGY', 'OPERABILITY', 'TRACEABILITY', + 'Product/technical gap baseline', ]) { expect(fitness).toContain(documentFamily); } diff --git a/src/workflowExactHead.test.ts b/src/workflowExactHead.test.ts index 8da26fe4..d1f05310 100644 --- a/src/workflowExactHead.test.ts +++ b/src/workflowExactHead.test.ts @@ -19,18 +19,102 @@ const VULNERABLE_PNPM_ACTION_SETUP_PIN = 'pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8'; const SETUP_NODE_PIN = 'actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0'; +const SETUP_PYTHON_PIN = + 'actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0'; +const EXPECTED_HEAD_REF = + 'ref: ${{ github.event.pull_request.head.sha || github.sha }}'; +const VERIFY_EXACT_CHECKOUT_STEP = [ + '- name: Verify exact checkout', + ' env:', + ' INKSPAN_EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}', + ' run: |', + ' actual_head="$(git rev-parse HEAD)"', + ' test "$actual_head" = "$INKSPAN_EXPECTED_HEAD_SHA"', +].join('\n'); + +/** Return one top-level workflow job without borrowing assertions from siblings. */ +function workflowJob(source: string, name: string, nextName?: string): string { + const startMarker = ` ${name}:\n`; + const start = source.indexOf(startMarker); + expect(start).toBeGreaterThan(-1); + if (nextName === undefined) { + return source.slice(start); + } + const end = source.indexOf(`\n ${nextName}:\n`, start + startMarker.length); + expect(end).toBeGreaterThan(start); + return source.slice(start, end); +} + +/** Require checkout -> exact-SHA verification -> first consumer in one job. */ +function expectExactCheckoutBeforeConsumer(job: string, consumer: string): void { + const checkout = job.indexOf(`- uses: ${CHECKOUT_PIN}`); + const verification = job.indexOf(VERIFY_EXACT_CHECKOUT_STEP); + const consumerIndex = job.indexOf(consumer); + + expect(checkout).toBeGreaterThan(-1); + expect(verification).toBeGreaterThan(checkout); + expect(consumerIndex).toBeGreaterThan(verification); + expect(job.match(new RegExp(CHECKOUT_PIN, 'g'))).toHaveLength(1); + expect(job.match(new RegExp(EXPECTED_HEAD_REF.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'))).toHaveLength(1); + expect(job.match(/persist-credentials: false/g)).toHaveLength(1); + expect(job.split(VERIFY_EXACT_CHECKOUT_STEP)).toHaveLength(2); + + const checkoutStep = job.slice(checkout, verification); + expect(checkoutStep).not.toContain('\n - '); +} + +const buildJob = workflowJob(workflow, 'build-and-test', 'browser-release-evidence'); +const browserJob = workflowJob(workflow, 'browser-release-evidence', 'office'); +const officeJob = workflowJob(workflow, 'office'); describe('exact-head CI workflow contract', () => { - it('uses a fixed runner and checks out the immutable current PR head', () => { + it('uses a fixed runner and checks out the immutable current PR head in every job', () => { expect(workflow).not.toContain('ubuntu-latest'); - expect(workflow.match(/runs-on: ubuntu-24\.04/g)).toHaveLength(3); - expect(workflow.match(new RegExp(CHECKOUT_PIN, 'g'))).toHaveLength(3); - expect( - workflow.match( - /ref: \$\{\{ github\.event\.pull_request\.head\.sha \|\| github\.sha \}\}/g, - ), - ).toHaveLength(3); - expect(workflow.match(/persist-credentials: false/g)).toHaveLength(3); + for (const job of [buildJob, browserJob, officeJob]) { + expect(job.match(/runs-on: ubuntu-24\.04/g)).toHaveLength(1); + expect(job).toContain(`- uses: ${CHECKOUT_PIN}`); + expect(job).toContain(EXPECTED_HEAD_REF); + expect(job).toContain('persist-credentials: false'); + } + }); + + it('runs exact-head CI for pull requests regardless of stacked base branch', () => { + const triggerStart = workflow.indexOf('on:\n'); + const triggerEnd = workflow.indexOf('\npermissions:', triggerStart); + expect(triggerStart).toBeGreaterThan(-1); + expect(triggerEnd).toBeGreaterThan(triggerStart); + const triggerBlock = workflow.slice(triggerStart, triggerEnd); + + expect(triggerBlock).toContain(' push:\n branches: [main]'); + const pullRequestLines = triggerBlock + .split('\n') + .filter((line) => line.startsWith(' pull_request:')); + expect(pullRequestLines).toEqual([' pull_request:']); + expect(triggerBlock).not.toMatch(/ pull_request:\n(?: .*\n)*? branches:/u); + }); + + it('binds timeout policy to the intended job instead of global counts', () => { + expect(buildJob.match(/timeout-minutes:/g)).toHaveLength(1); + expect(buildJob).toContain('timeout-minutes: 30'); + expect(browserJob.match(/timeout-minutes:/g)).toHaveLength(1); + expect(browserJob).toContain('timeout-minutes: 60'); + expect(officeJob.match(/timeout-minutes:/g)).toHaveLength(1); + expect(officeJob).toContain('timeout-minutes: 30'); + }); + + it('verifies each runtime checkout immediately before repository-consuming setup', () => { + expectExactCheckoutBeforeConsumer( + buildJob, + `- uses: ${PNPM_ACTION_SETUP_PIN}`, + ); + expectExactCheckoutBeforeConsumer( + browserJob, + `- uses: ${PNPM_ACTION_SETUP_PIN}`, + ); + expectExactCheckoutBeforeConsumer( + officeJob, + `- uses: ${SETUP_PYTHON_PIN}`, + ); }); it('keeps the workflow read-only and hash-pins every third-party action', () => { @@ -51,12 +135,16 @@ describe('exact-head CI workflow contract', () => { }); it('does not bootstrap pnpm through the vulnerable action release', () => { - expect(workflow.match(new RegExp(PNPM_ACTION_SETUP_PIN, 'g'))).toHaveLength(2); + expect(buildJob).toContain(PNPM_ACTION_SETUP_PIN); + expect(browserJob).toContain(PNPM_ACTION_SETUP_PIN); + expect(officeJob).not.toContain(PNPM_ACTION_SETUP_PIN); expect(workflow).not.toContain(VULNERABLE_PNPM_ACTION_SETUP_PIN); }); it('uses the current native-Node-24 setup-node release in every JavaScript job', () => { - expect(workflow.match(new RegExp(SETUP_NODE_PIN, 'g'))).toHaveLength(2); + expect(buildJob).toContain(SETUP_NODE_PIN); + expect(browserJob).toContain(SETUP_NODE_PIN); + expect(officeJob).not.toContain(SETUP_NODE_PIN); expect(workflow).not.toContain( 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0', );