diff --git a/.jules/palette.md b/.jules/palette.md index 0bbf5248..9b83044d 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -115,3 +115,7 @@ ## $(date +%Y-%m-%d) - Prevent accidental data loss in inline editors **Learning:** Forms that take a long time to fill out (like a WBS editor) are prone to accidental closure by users pressing `Escape` or clicking cancel. This causes immediate data loss without any warning, resulting in frustration. **Action:** When working on editors that can be dismissed, track whether the user has modified any fields compared to their initial state. If there are changes, intercept the close action and present a confirmation dialog (`window.confirm`) to ensure they really want to discard their edits. Bypass this for intentional saves or explicit data overrides. + +## 2026-06-30 - Improve Screen Reader UX for Toast Notifications +**Learning:** Toast messages using only `aria-live="polite"` might not be announced correctly or entirely by all screen readers, especially if the content changes dynamically. +**Action:** Add `role="status"` and `aria-atomic="true"` to toast container elements to ensure assistive technologies consistently announce the full content of the notification. diff --git a/CHANGELOG.md b/CHANGELOG.md index bc41a659..787ee51b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security -- Made contextual-orchestrator briefing requests fail closed unless an authenticated endpoint is configured. Deterministic generated text is restricted to explicit `SCOPEWEAVE_DEV=1`, message/provider responses are bounded and validated, and non-loopback HTTP transport is rejected. - Made `SCOPEWEAVE_JWT_SECRET` mandatory at startup and rejected weak or unexpanded placeholder values so production deployments fail closed. - Neutralized audit-log CSV formulas even when executable prefixes are hidden @@ -53,10 +52,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Accepted XML whitespace before exact Microsoft Project element delimiters - while preserving the linear, regex-free import scanner and rejecting - attributes, longer names, non-XML whitespace, nested unmatched blocks, and - truncated input. - Attachment-list status refresh now removes the per-row database lookup, uses a configurable bounded worker pool with per-item abortable timeouts and a request-wide latency budget, preserves stale status after downstream, diff --git a/cloud-sync.js b/cloud-sync.js index 0e015ebe..9016cfbf 100644 --- a/cloud-sync.js +++ b/cloud-sync.js @@ -741,54 +741,33 @@ function openReportModal() { export function parseMsProjectXml(xml) { // Fully linear extract (indexOf/slice) — no dynamic RegExp and no lazy // [\s\S]*? block collectors (those can quadratic-backtrack on truncated input). - const isXmlWhitespace = (charCode) => ( - charCode === 0x20 || charCode === 0x09 || charCode === 0x0d || charCode === 0x0a - ); - const findTagBoundary = (source, name, from, closing = false) => { - const prefix = `<${closing ? '/' : ''}${name}`; - let searchFrom = from; - for (;;) { - const start = source.indexOf(prefix, searchFrom); - if (start === -1) return null; - let delimiter = start + prefix.length; - while (delimiter < source.length && isXmlWhitespace(source.charCodeAt(delimiter))) { - delimiter += 1; - } - if (source.charCodeAt(delimiter) === 0x3e) { - return { start, end: delimiter + 1 }; - } - // Reject attributes, longer names, and non-XML whitespace while advancing - // past every inspected byte so malformed candidates are never rescanned. - searchFrom = Math.max(delimiter + 1, start + prefix.length); - } - }; const tag = (block, name) => { - const opening = findTagBoundary(block, name, 0); - if (!opening) return ''; - const closing = findTagBoundary(block, name, opening.end, true); - const nextOpening = findTagBoundary(block, name, opening.end); - if (!closing || (nextOpening && nextOpening.start < closing.start)) return ''; - return block.slice(opening.end, closing.start).trim(); + const openingTag = `<${name}>`; + const closingTag = ``; + const valueStart = block.indexOf(openingTag); + if (valueStart === -1) return ''; + const contentStart = valueStart + openingTag.length; + const valueEnd = block.indexOf(closingTag, contentStart); + return valueEnd === -1 ? '' : block.slice(contentStart, valueEnd).trim(); }; - const collectBlocks = (source, name) => { + const collectBlocks = (source, openTag, closeTag) => { const out = []; let from = 0; for (;;) { - const opening = findTagBoundary(source, name, from); - if (!opening) break; - const closing = findTagBoundary(source, name, opening.end, true); - const nextOpening = findTagBoundary(source, name, opening.end); - // Incomplete or nested same-name block: stop at the first unmatched - // opening tag instead of pairing it with a later block's closing tag. - if (!closing || (nextOpening && nextOpening.start < closing.start)) break; - out.push(source.slice(opening.start, closing.end)); - from = closing.end; + const start = source.indexOf(openTag, from); + if (start === -1) break; + const contentStart = start + openTag.length; + const end = source.indexOf(closeTag, contentStart); + // Incomplete open tag: stop linearly (do not rescan the remainder). + if (end === -1) break; + out.push(source.slice(start, end + closeTag.length)); + from = end + closeTag.length; } return out; }; const predecessorIds = (block) => { const ids = []; - for (const link of collectBlocks(block, 'PredecessorLink')) { + for (const link of collectBlocks(block, '', '')) { const uid = tag(link, 'PredecessorUID'); if (/^\d+$/.test(uid)) ids.push(`msp-${uid}`); } @@ -800,7 +779,7 @@ export function parseMsProjectXml(xml) { const day = (s) => (/^\d{4}-\d{2}-\d{2}/.test(s) ? s.slice(0, 10) : ''); const tasks = []; const parents = {}; // depth -> last task id at that depth - const blocks = collectBlocks(String(xml || ''), 'Task'); + const blocks = collectBlocks(String(xml || ''), '', ''); for (const block of blocks) { const uid = tag(block, 'UID'); const name = unescape(tag(block, 'Name')); diff --git a/docs/doctoring/ms-project-xml-import-boundary.md b/docs/doctoring/ms-project-xml-import-boundary.md deleted file mode 100644 index 0a8fa5aa..00000000 --- a/docs/doctoring/ms-project-xml-import-boundary.md +++ /dev/null @@ -1,63 +0,0 @@ -# Microsoft Project XML delimiter boundary - -## Decision - -ScopeWeave's Microsoft Project import profile accepts XML whitespace between an -exact supported element name and the closing `>` delimiter. The accepted code -points are: - -- U+0020 SPACE; -- U+0009 CHARACTER TABULATION; -- U+000D CARRIAGE RETURN; and -- U+000A LINE FEED. - -The parser deliberately does not become a general XML processor. It recognizes -only the exact `Task`, `PredecessorLink`, and scalar element names already used -by the import adapter. Attributes, namespace prefixes, longer lookalike names, -non-XML whitespace, self-closing forms, nested same-name blocks, and truncated -blocks are rejected or yield no value under this narrow profile. - -## Security and complexity boundary - -The scanner remains monotonic and regex-free. It advances through every rejected -candidate and uses bounded `indexOf()` and `slice()` operations rather than -constructing dynamic regular expressions or lazy whole-document block matches. -This preserves the existing denial-of-service boundary for malformed or -adversarial uploads. - -An unmatched outer element cannot consume a later nested element's closing tag. -If another same-name opening appears before the candidate closing tag, block -collection stops at the unmatched outer element instead of silently producing a -mis-parented task. - -## Executable evidence - -`tests/unit/msproject.test.mjs` covers: - -- space, tab, carriage-return, and line-feed delimiters; -- scalar and predecessor-link elements using each allowed delimiter; -- an actual U+000B vertical tab, which is not XML whitespace; -- attributes and longer element names; -- truncated and repeated unclosed task blocks; -- nested same-name openings before a closing element; and -- the existing valid import and predecessor contracts. - -The test is already part of the full unit and coverage command paths. No package -or lockfile change is required. - -## Compatibility and rollback - -The change broadens acceptance only for documents that are conformant with the -XML whitespace production at the delimiter positions used by this adapter. -Existing byte-exact exports retain the same task identifiers, names, dates, -parents, progress, and predecessor values. - -Rollback must revert the scanner, focused tests, security documentation, -CHANGELOG entry, and this record together. Reintroducing byte-exact delimiters -would again reject standards-compliant Microsoft Project exports that contain -formatting whitespace before `>`. - -## Reference - -World Wide Web Consortium. (2008). *Extensible Markup Language (XML) 1.0 -(Fifth Edition)*. https://www.w3.org/TR/2008/REC-xml-20081126/ diff --git a/docs/orchestrator-production.md b/docs/orchestrator-production.md deleted file mode 100644 index c2c4c5c7..00000000 --- a/docs/orchestrator-production.md +++ /dev/null @@ -1,68 +0,0 @@ -# contextual-orchestrator Production Contract - -ScopeWeave delegates AI briefing work to `contextual-orchestrator`; it does not -silently replace unavailable production inference with deterministic text. - -## Required environment - -```text -ORCHESTRATOR_URL=https://orchestrator.example -ORCHESTRATOR_TOKEN= -ORCHESTRATOR_MODEL=contextual-orchestrator -``` - -`ORCHESTRATOR_URL` is a provider **origin**, not an arbitrary request URL. It -must not contain user-info credentials, a non-root path, a query string, or a -fragment. ScopeWeave owns the fixed `/v1/chat/completions` request path and keeps -bearer credentials in `ORCHESTRATOR_TOKEN`; operator URL text therefore cannot -silently alter request routing or mix endpoint authority with credentials. -Custom ports remain valid because they are part of the origin. - -Production requests fail closed when the endpoint or bearer token is absent. -Non-loopback HTTP endpoints are rejected, requests are bounded to 120 seconds, -message count and content size are validated, provider payloads are not exposed -in errors, and an empty or malformed assistant response is never reported as a -successful briefing. - -Provider response bodies have a hard 1 MiB caller-side byte budget **while they -are being read**. An oversized numeric `Content-Length` is rejected before body -allocation; when the header is absent or inaccurate, the stream reader counts -bytes incrementally, cancels the body as soon as the budget is exceeded, and -never buffers an unbounded provider payload before applying the limit. Empty, -non-stream-readable, malformed-length, non-JSON, oversized, or structurally -invalid responses fail with stable operator-safe errors rather than exposing -provider payload details. - -The deterministic adapter is available only when `SCOPEWEAVE_DEV=1` and the -endpoint is absent. That variable must never be set in staging or production. - -## Orchestration responsibility - -ScopeWeave intentionally sends only a versioned OpenAI-compatible request to -the orchestration service. Model selection, single-model versus multi-agent -allocation, task decomposition, role-specific reasoning effort, recursion -limits, access lists, synthesis, and verification belong to -`contextual-orchestrator`, where they can be evaluated and evolved centrally. -This separation is consistent with learned coordination research: Conductor -learns task decompositions, worker assignments, communication topologies, and -recursive test-time scaling; TRINITY adaptively assigns Thinker, Worker, and -Verifier roles over multiple turns; Fugu operationalizes learned orchestration -behind one model-compatible API. - -ScopeWeave therefore does not hard-code a local fake solver, fixed topology, or -provider-specific bypass. Changes to orchestration policy require benchmarked -ablation evidence in `contextual-orchestrator`, including single-model, -parallel, sequential, hierarchical, recursive, and verifier-assisted paths. - -## APA 7th references - -Nielsen, S., Cetin, E., Schwendeman, P., Sun, Q., Xu, J., & Tang, Y. (2025). -*Learning to orchestrate agents in natural language with the Conductor*. -arXiv. https://doi.org/10.48550/arXiv.2512.04388 - -Sakana AI. (2026). *Sakana Fugu: Multi-agent system as a model*. -https://sakana.ai/fugu/ - -Xu, J., Sun, Q., Schwendeman, P., Nielsen, S., Cetin, E., & Tang, Y. (2025). -*TRINITY: An evolved LLM coordinator*. arXiv. -https://doi.org/10.48550/arXiv.2512.04695 diff --git a/docs/security.md b/docs/security.md index 0ceee972..5b21a5e6 100644 --- a/docs/security.md +++ b/docs/security.md @@ -19,7 +19,7 @@ Every user-controlled CSV cell is neutralized when, after optional leading white ## XML imports -Microsoft Project XML extraction uses bounded `indexOf`/`slice` loops. Opening and closing `Task`, `PredecessorLink`, and scalar tags accept only XML whitespace (space, tab, carriage return, or line feed) between the exact element name and `>`. Attributes, longer names, and other whitespace code points are not accepted by this deliberately narrow import profile. Dynamic regular expressions and lazy whole-document block collectors are prohibited because truncated or adversarial input can cause catastrophic backtracking. +Microsoft Project XML extraction uses bounded `indexOf`/`slice` loops. Dynamic regular expressions and lazy whole-document block collectors are prohibited because truncated or adversarial input can cause catastrophic backtracking. ## Release verification diff --git a/index.html b/index.html index a7f4b49c..1a83c546 100644 --- a/index.html +++ b/index.html @@ -95,7 +95,7 @@

ScopeWeave Planner

-
+