From 7fc7693aba990925d35010fc0644108644e8eb3c Mon Sep 17 00:00:00 2001 From: JasonStys Date: Tue, 10 Nov 2026 02:03:49 -0800 Subject: [PATCH 1/2] docs: publish verified security and native validation editions --- .gitattributes | 2 + README.md | 123 +++++---- docs/code-index.md | 104 ++++++++ docs/handbook.md | 67 +++++ .../documentation-refresh-2026-11-09.md | 55 ++++ ...tive-registration-completion-2026-11-10.md | 60 ++++- docs/reports/project-guide.docx | Bin 0 -> 45782 bytes docs/reports/publications.json | 124 +++++++++ docs/reports/validation-evidence.xlsx | Bin 0 -> 7695 bytes docs/reports/validation-review.docx | Bin 0 -> 48485 bytes pyproject.toml | 2 +- tests/test_publications.py | 252 ++++++++++++++++++ 12 files changed, 715 insertions(+), 74 deletions(-) create mode 100644 docs/handbook.md create mode 100644 docs/reports/documentation-refresh-2026-11-09.md create mode 100644 docs/reports/project-guide.docx create mode 100644 docs/reports/publications.json create mode 100644 docs/reports/validation-evidence.xlsx create mode 100644 docs/reports/validation-review.docx create mode 100644 tests/test_publications.py diff --git a/.gitattributes b/.gitattributes index 70f4e6e..f1b36f2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ # Worker resources must remain Linux-compatible even inside Windows/macOS installers. runner/** text eol=lf +*.docx binary +*.xlsx binary diff --git a/README.md b/README.md index 5e53ffb..fd2b165 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,19 @@ Download OS-specific installers/portable packages from [Releases](https://github **Located diagnostics:** static review and execution evidence list original filenames and local line locations, with snapshot hashes. Python checks include auxiliary-file syntax, possibly unused imports/locals/functions and directly unreachable statements. Every other supported execution language now has native unused-code candidates and actual language-specific coverage. UI/HTML show missed source lines, genuine branch arcs or native branch IDs/counts, and explicit partial/unknown states. SQL measures completed statement starts, Bash command-entry lines and assembly instruction-backed debug lines—not universal statement/branch coverage. Unsupported locations and wrong-answer failures without source references stay unlocated. See the [language-by-language tools and limits](docs/polyglot-locations.md), [file/line contract](docs/located-diagnostics.md) and [validation](docs/validation-polyglot.md). No tool guarantees discovery or root-cause localization of every bug. -| Feature | Implemented contract | -|---|---| -| Private accounts | Real alias/password registration, hashed sessions and one-use login recovery. Independent browser-derived vault key, authenticated AES-256-GCM locked source/history/provider archives and per-session unlock proof. No developer plaintext read API. Trusted local service and OS can access active plaintext; not browser-only E2EE. | -| Desktop application | Electron shell and bundled Python service; one-click Windows installer plus OS-specific Linux/macOS builds. No IntelliJ or separately installed Python required for the UI. Unsigned development packages. | -| Web application | Same responsive editor/API served on loopback. Narrow-screen browser support; not a native Android/iOS installer or public hosted service. | -| Polyglot execution | Fixed CLI adapters for Python, JavaScript, TypeScript, C/C++, Java, C#, Rust, PHP, Bash, SQLite SQL and GNU x86-64 assembly. Actual toolchains live inside the worker image. | -| Testing | Approved CLI oracles and reviewed pytest harnesses, including subprocess harnesses for non-Python targets. 48-category applicability catalog; human, hardware, cloud/load and specialized tool checks need independent integrations. | -| Coverage/mutation | Exact original source locations from native collectors for all twelve adapters; per-file hashes, missed/executed lines, language-specific metric labels and honest unknown/partial states. Separate from baseline timing/RSS. Python additionally supports up to eight operator mutants. Approved CLI coverage excludes generated pytest; no fabricated MC/DC, universal dead-code or universal mutation scores. | -| Fuzzing | Seeded black-box generation, mutation, dictionary, boundaries and CSV/SQL grammar; instrumented libFuzzer/AFL++ for reviewed C/C++ harnesses and Atheris for Python harnesses. Differential/metamorphic/stateful relations require custom oracles. | -| AI routing | Account-owned OpenAI-compatible, Anthropic and Ollama endpoints/keys, four task routes and destination-version-bound consent. Keys stay server-side after explicit settings submission, outside histories/exports/workers. Protocol tests use mocks, not a claim of live model quality. | -| Metrics/ranking | Repeated process wall times, peak child RSS, source bytes and transparent long-line proxy. Passing independent cases is mandatory. No syntax-based Big-O claims or automatic “best” model claims. | -| Training | Rights-checked, project-disjoint JSONL preparation, actual small causal-model creation or approved local-model fine-tuning, safe-tensor output and authenticated local inference API. Tiny synthetic smoke training is not a useful general coding model. | -| CI | SHA-pinned, read-only Actions validate controller/browser behavior, real worker scenarios, training smoke and native packages. Push analysis retains generated tests/candidate evidence when a provider is explicitly configured; never auto-commits model changes. | +| Feature | Implemented contract | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Private accounts | Real alias/password registration, hashed sessions and one-use login recovery. Independent browser-derived vault key, authenticated AES-256-GCM locked source/history/provider archives and per-session unlock proof. No developer plaintext read API. Trusted local service and OS can access active plaintext; not browser-only E2EE. | +| Desktop application | Electron shell and bundled Python service; one-click Windows installer plus OS-specific Linux/macOS builds. No IntelliJ or separately installed Python required for the UI. Unsigned development packages. | +| Web application | Same responsive editor/API served on loopback. Narrow-screen browser support; not a native Android/iOS installer or public hosted service. | +| Polyglot execution | Fixed CLI adapters for Python, JavaScript, TypeScript, C/C++, Java, C#, Rust, PHP, Bash, SQLite SQL and GNU x86-64 assembly. Actual toolchains live inside the worker image. | +| Testing | Approved CLI oracles and reviewed pytest harnesses, including subprocess harnesses for non-Python targets. 48-category applicability catalog; human, hardware, cloud/load and specialized tool checks need independent integrations. | +| Coverage/mutation | Exact original source locations from native collectors for all twelve adapters; per-file hashes, missed/executed lines, language-specific metric labels and honest unknown/partial states. Separate from baseline timing/RSS. Python additionally supports up to eight operator mutants. Approved CLI coverage excludes generated pytest; no fabricated MC/DC, universal dead-code or universal mutation scores. | +| Fuzzing | Seeded black-box generation, mutation, dictionary, boundaries and CSV/SQL grammar; instrumented libFuzzer/AFL++ for reviewed C/C++ harnesses and Atheris for Python harnesses. Differential/metamorphic/stateful relations require custom oracles. | +| AI routing | Account-owned OpenAI-compatible, Anthropic and Ollama endpoints/keys, four task routes and destination-version-bound consent. Keys stay server-side after explicit settings submission, outside histories/exports/workers. Protocol tests use mocks, not a claim of live model quality. | +| Metrics/ranking | Repeated process wall times, peak child RSS, source bytes and transparent long-line proxy. Passing independent cases is mandatory. No syntax-based Big-O claims or automatic “best” model claims. | +| Training | Rights-checked, project-disjoint JSONL preparation, actual small causal-model creation or approved local-model fine-tuning, safe-tensor output and authenticated local inference API. Tiny synthetic smoke training is not a useful general coding model. | +| CI | SHA-pinned, read-only Actions validate controller/browser behavior, real worker scenarios, training smoke and native packages. Push analysis retains generated tests/candidate evidence when a provider is explicitly configured; never auto-commits model changes. | Binary formats can be retained as fixtures. There is no promise to understand every document, image, encrypted archive or binary. Supply text requirements and explicit harnesses for formats without a parser; unsupported extraction must not silently succeed. @@ -38,10 +38,10 @@ The [source inspection workflow](docs/source-navigation.md) explains stale/legac ## Run and install -The 0.4.1 source prepares a security hotfix. Its [release notes](docs/releases/v0.4.1.md) +Version 0.4.1 includes bounded worker-report reads and verified source review. Its [release notes](docs/releases/v0.4.1.md) and [full-main finding/validation report](docs/reports/codeql-main-fixes-2026-11-09.md) describe the bounded worker reader and the fresh native/download checks required -before publication. A source version change alone does not replace a published download. +before release promotion. Check Releases for the currently published downloads; a source version alone does not replace one. For end users, use the checksummed OS-specific packages in Releases. The UI runtime is bundled. Open the app's **Code Flow → Open local website** menu for browser use; no login token appears in the URL. Compiler jobs require an isolated local Unix/named-pipe Docker engine and the trusted worker image. Missing isolation is blocked, never passed; there is no host-execution fallback. Save/edit/static review/history/help remain usable without Docker. @@ -49,49 +49,49 @@ Source-development instructions, provider setup, first-run diagnostics and packa ## Source map -| Location | Responsibility | -|---|---| -| `studio/accounts.py`, `studio/permissions.py` | scrypt logins, session/recovery digests, bounded attempts and private per-OS data permissions. | -| `studio/private_api.py` | Account-only HTTP/CSRF boundary, own provider settings, per-session key proof, backup/recovery/deletion and scoped controller proxy. | -| `studio/private_workspace.py`, `studio/vault.py` | Single-instance ownership, account-specific engine lifecycle, authenticated streaming encryption, SQLite online snapshots and safe restoration. | -| `studio/private_cli.py` | Token-free loopback account service, dedicated data root and exact-parent graceful shutdown monitor. | -| `ui/Account.tsx` | Sign-in/unlock/recovery, contextual help, encrypted backup, private AI configuration and creator contact. | -| `studio/models.py` | Validated flat-file, project, case, preference and AI proposal schemas; size/path limits. | -| `studio/api.py` | Authenticated loopback API, bounded jobs, history, reports, comparisons and AI tasks. | -| `studio/store.py` | SQLite transactions, append-only revisions, immutable baseline oracles and interrupted-job recovery. | -| `studio/execution.py` | Fixed container flags, readiness checks, output/deadline bounds and exact owned-container cleanup. | -| `studio/analysis.py` | Non-executing source heuristics and correctness-gated weighted comparisons. | -| `studio/source_checks.py`, `studio/locations.py`, `ui/Locations.tsx` | Python syntax/unused/unreachable candidates, bounded tool-output source references, file/category filters and clickable exact-location/coverage evidence views. | -| `ui/source-navigation.ts`, `ui/SourceInspector.tsx` | Exact UTF-8 SHA-256 verification, bounded immutable source inspection, nearby-line browsing and guarded navigation to an unchanged current draft. | -| `studio/providers.py` | Private provider configuration, task routes, consent, bounded requests and proposal schema validation. | -| `studio/catalog.py` | All 48 supplied test categories with applicability/integration requirements. | -| `studio/reports.py` | Escaped, inert HTML/JSON/ZIP evidence exports. | -| `studio/cli.py` | Shared web launcher and manifest-to-report CI command. | -| `runner/Dockerfile`, `runner/worker.py` | Trusted compiler image and isolated compile/test/coverage/mutation/fuzz/benchmark execution. | -| `runner/language_checks.py`, `runner/node_checks.cjs` | Bounded native tool adapters, source binding, fixed ESLint configuration and V8 source-map conversion. | -| `runner/sql_runtime.py`, `runner/php_coverage.php`, `runner/bash_coverage.sh`, `runner/java-unused.xml` | SQLite statement anchors, pre-compilation Xdebug hook, transient Bash trace collector and fixed PMD unused rules. | -| `runner/tools/package.json`, `runner/tools/package-lock.json` | Pinned/audited worker-only JS analyzer dependencies; not desktop runtime dependencies. | -| `ui/App.tsx` | Workspace, cases, priorities, live suggestions, proposal approval, evidence and history views. | -| `ui/main.tsx`, `ui/api.ts` | Offline editor worker registry and authenticated same-origin client/downloads. | -| `ui/demo.ts`, `ui/types.ts` | Synthetic CSV example and renderer contracts. | -| `ui/index.html`, `ui/style.css` | Local HTML entry and responsive accessible layout. | -| `desktop/main.cjs` | Sandboxed Electron renderer and private bundled-service lifecycle. | -| `training/dataset.py`, `training/train.py`, `training/serve.py` | Provenance/split validation, parameterized real training, local model API. | -| `examples/projects.py`, `examples/web-demo.project.json` | Compiler-contract fixtures and the exact synthetic CSV source/manifest used by the UI, checked in CI. | -| `examples/language_locations.py`, `tests/test_native_locations.py` | Independent polyglot unused/missed-line oracles and pure native-report parser/security regressions. | -| `examples/training.json`, `examples/training.jsonl` | Tiny rights-owned training smoke parameters and data; no quality claims. | -| `scripts/frozen_entry.py` | Frozen-service entry point for installers. | -| `scripts/verify_electron.cjs` | Fail-fast pinned Electron path/embedded-version verification; official SDK installation happens before timed native tests. | -| `scripts/worker_checks.py` | Actual isolated compiler/fuzz/security-boundary integration scenarios. | -| `runner/sanitizer_probe.py`, `scripts/worker_analyzer_checks.cjs` | Trusted same-container sanitizer-startup/fault controls and independent exact-line JS/TS checks against the separately audited worker dependency graph. | -| `scripts/private_worker_checks.py` | Real twelve-adapter execution through authenticated account API and encrypted-history reopen. | -| `scripts/license_bundle.py`, `scripts/release_archive.py` | Generated third-party notices, portable app packages and SHA-256 checksums; never package user accounts. | -| `scripts/release_gate.py`, `scripts/draft_lookup.py` | Current-main source/CodeQL prerequisites, strict authenticated numeric draft identity, embedded source/worker provenance, bounded fixed-host uploads and draft-only final verification. | -| `tests/test_release_gate.py`, `tests/test_draft_lookup.py`, `tests/node-report.test.cjs` | Independent GitHub response/policy mutation fixtures, real archive/transport negative oracles and actual descriptor-bound worker JSON read regressions. | -| `scripts/push_analysis.py` | Push generation/test/candidate workflow without automatic source promotion. | -| `tests/` | Controller/security/history/provider/metric tests, browser flows and native launch smoke. | -| `.github/workflows/` | Validation, real isolated workers, training and native OS-package workflows. | -| `pyproject.toml`, `package.json`, lockfile, TypeScript/Vite/Playwright configs | Dependency and reproducible build/test definitions. | +| Location | Responsibility | +| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `studio/accounts.py`, `studio/permissions.py` | scrypt logins, session/recovery digests, bounded attempts and private per-OS data permissions. | +| `studio/private_api.py` | Account-only HTTP/CSRF boundary, own provider settings, per-session key proof, backup/recovery/deletion and scoped controller proxy. | +| `studio/private_workspace.py`, `studio/vault.py` | Single-instance ownership, account-specific engine lifecycle, authenticated streaming encryption, SQLite online snapshots and safe restoration. | +| `studio/private_cli.py` | Token-free loopback account service, dedicated data root and exact-parent graceful shutdown monitor. | +| `ui/Account.tsx` | Sign-in/unlock/recovery, contextual help, encrypted backup, private AI configuration and creator contact. | +| `studio/models.py` | Validated flat-file, project, case, preference and AI proposal schemas; size/path limits. | +| `studio/api.py` | Authenticated loopback API, bounded jobs, history, reports, comparisons and AI tasks. | +| `studio/store.py` | SQLite transactions, append-only revisions, immutable baseline oracles and interrupted-job recovery. | +| `studio/execution.py` | Fixed container flags, readiness checks, output/deadline bounds and exact owned-container cleanup. | +| `studio/analysis.py` | Non-executing source heuristics and correctness-gated weighted comparisons. | +| `studio/source_checks.py`, `studio/locations.py`, `ui/Locations.tsx` | Python syntax/unused/unreachable candidates, bounded tool-output source references, file/category filters and clickable exact-location/coverage evidence views. | +| `ui/source-navigation.ts`, `ui/SourceInspector.tsx` | Exact UTF-8 SHA-256 verification, bounded immutable source inspection, nearby-line browsing and guarded navigation to an unchanged current draft. | +| `studio/providers.py` | Private provider configuration, task routes, consent, bounded requests and proposal schema validation. | +| `studio/catalog.py` | All 48 supplied test categories with applicability/integration requirements. | +| `studio/reports.py` | Escaped, inert HTML/JSON/ZIP evidence exports. | +| `studio/cli.py` | Shared web launcher and manifest-to-report CI command. | +| `runner/Dockerfile`, `runner/worker.py` | Trusted compiler image and isolated compile/test/coverage/mutation/fuzz/benchmark execution. | +| `runner/language_checks.py`, `runner/node_checks.cjs` | Bounded native tool adapters, source binding, fixed ESLint configuration and V8 source-map conversion. | +| `runner/sql_runtime.py`, `runner/php_coverage.php`, `runner/bash_coverage.sh`, `runner/java-unused.xml` | SQLite statement anchors, pre-compilation Xdebug hook, transient Bash trace collector and fixed PMD unused rules. | +| `runner/tools/package.json`, `runner/tools/package-lock.json` | Pinned/audited worker-only JS analyzer dependencies; not desktop runtime dependencies. | +| `ui/App.tsx` | Workspace, cases, priorities, live suggestions, proposal approval, evidence and history views. | +| `ui/main.tsx`, `ui/api.ts` | Offline editor worker registry and authenticated same-origin client/downloads. | +| `ui/demo.ts`, `ui/types.ts` | Synthetic CSV example and renderer contracts. | +| `ui/index.html`, `ui/style.css` | Local HTML entry and responsive accessible layout. | +| `desktop/main.cjs` | Sandboxed Electron renderer and private bundled-service lifecycle. | +| `training/dataset.py`, `training/train.py`, `training/serve.py` | Provenance/split validation, parameterized real training, local model API. | +| `examples/projects.py`, `examples/web-demo.project.json` | Compiler-contract fixtures and the exact synthetic CSV source/manifest used by the UI, checked in CI. | +| `examples/language_locations.py`, `tests/test_native_locations.py` | Independent polyglot unused/missed-line oracles and pure native-report parser/security regressions. | +| `examples/training.json`, `examples/training.jsonl` | Tiny rights-owned training smoke parameters and data; no quality claims. | +| `scripts/frozen_entry.py` | Frozen-service entry point for installers. | +| `scripts/verify_electron.cjs` | Fail-fast pinned Electron path/embedded-version verification; official SDK installation happens before timed native tests. | +| `scripts/worker_checks.py` | Actual isolated compiler/fuzz/security-boundary integration scenarios. | +| `runner/sanitizer_probe.py`, `scripts/worker_analyzer_checks.cjs` | Trusted same-container sanitizer-startup/fault controls and independent exact-line JS/TS checks against the separately audited worker dependency graph. | +| `scripts/private_worker_checks.py` | Real twelve-adapter execution through authenticated account API and encrypted-history reopen. | +| `scripts/license_bundle.py`, `scripts/release_archive.py` | Generated third-party notices, portable app packages and SHA-256 checksums; never package user accounts. | +| `scripts/release_gate.py`, `scripts/draft_lookup.py` | Current-main source/CodeQL prerequisites, strict authenticated numeric draft identity, embedded source/worker provenance, bounded fixed-host uploads and draft-only final verification. | +| `tests/test_release_gate.py`, `tests/test_draft_lookup.py`, `tests/node-report.test.cjs` | Independent GitHub response/policy mutation fixtures, real archive/transport negative oracles and actual descriptor-bound worker JSON read regressions. | +| `scripts/push_analysis.py` | Push generation/test/candidate workflow without automatic source promotion. | +| `tests/` | Controller/security/history/provider/metric tests, browser flows and native launch smoke. | +| `.github/workflows/` | Validation, real isolated workers, training and native OS-package workflows. | +| `pyproject.toml`, `package.json`, lockfile, TypeScript/Vite/Playwright configs | Dependency and reproducible build/test definitions. | Indexed Python/TypeScript/JavaScript headers point to the checked symbol/variable line map; the small Bash/PHP hooks describe their local variables inline. [Architecture](docs/architecture.md) and the [polyglot decision](docs/adr-polyglot-evidence.md) explain trade-offs; [testing](docs/testing.md) separates test claims from evidence. @@ -101,7 +101,6 @@ problems remain visible as failures until the actual isolated acceptance checks Historical 0.1 bearer-token CLI mode remains explicitly opt-in for existing automation; it is not account mode. No automatic import of legacy plaintext projects or global AI keys occurs. - ## CodeQL source security review The [CodeQL guide](docs/codeql.md) defines genuine Python, JavaScript/TypeScript and @@ -109,3 +108,9 @@ GitHub Actions `security-extended` analysis, review steps and unsupported scope. The [dated scan report](docs/reports/codeql-2026-11-09.md) distinguishes actual results from pending/missing evidence. Static scanning supplements the existing runtime, dependency, privacy and isolation checks; it does not certify every input or platform. + +## Documentation editions + +Start with the [organized handbook](docs/handbook.md) for current usage, design, security and test guidance. Reviewed downloadable editions: [project-guide.docx](docs/reports/project-guide.docx) · [validation-review.docx](docs/reports/validation-review.docx) · [validation-evidence.xlsx](docs/reports/validation-evidence.xlsx). + +[Publication identities and source scope](docs/reports/publications.json) · [Documentation validation record](docs/reports/documentation-refresh-2026-11-09.md). Historical results retain their own tested revision and date. diff --git a/docs/code-index.md b/docs/code-index.md index 639166c..242c7cf 100644 --- a/docs/code-index.md +++ b/docs/code-index.md @@ -2995,6 +2995,110 @@ Generated declaration locations. Python uses AST; JS/TS uses declaration-line ma | variable | `real` | 148 | | parameter | `kwargs` | 150 | +## tests/test_publications.py + +| Kind | Declaration | Line | +|---|---|---| +| variable | `FILES` | 26 | +| variable | `NS` | 27 | +| function | `validate_publications(root)` | 30 | +| parameter | `root` | 30 | +| variable | `reports` | 32 | +| variable | `manifest` | 33 | +| variable | `source` | 34 | +| variable | `records` | 41 | +| variable | `r` | 42 | +| variable | `workflows` | 44 | +| variable | `r` | 45 | +| variable | `r` | 56 | +| variable | `record` | 59 | +| variable | `stream` | 62 | +| variable | `payload` | 63 | +| variable | `package` | 70 | +| variable | `members` | 71 | +| variable | `m` | 72 | +| variable | `names` | 72 | +| variable | `required` | 73 | +| variable | `m` | 81 | +| variable | `xml` | 83 | +| variable | `member` | 84 | +| variable | `lower` | 85 | +| variable | `marker` | 87 | +| variable | `text` | 97 | +| variable | `marker` | 98 | +| variable | `relation` | 102 | +| variable | `approval` | 110 | +| variable | `count` | 111 | +| variable | `n` | 115 | +| variable | `name` | 119 | +| variable | `node` | 119 | +| variable | `tables` | 119 | +| variable | `t` | 123 | +| variable | `cells` | 126 | +| variable | `node` | 128 | +| variable | `c` | 129 | +| variable | `n` | 132 | +| variable | `row` | 133 | +| variable | `expected` | 134 | +| variable | `actual` | 140 | +| variable | `c` | 140 | +| function | `copy_publications(tmp_path)` | 145 | +| parameter | `tmp_path` | 145 | +| variable | `source` | 147 | +| variable | `target` | 148 | +| variable | `name` | 150 | +| function | `rewrite_package(root, filename, change)` | 155 | +| parameter | `change` | 155 | +| parameter | `filename` | 155 | +| parameter | `root` | 155 | +| variable | `path` | 157 | +| variable | `original` | 158 | +| variable | `entries` | 159 | +| variable | `m` | 159 | +| variable | `package` | 161 | +| variable | `content` | 162 | +| variable | `name` | 162 | +| variable | `manifest_path` | 164 | +| variable | `manifest` | 165 | +| variable | `r` | 166 | +| variable | `record` | 166 | +| variable | `payload` | 167 | +| function | `test_actual_reviewed_publications_are_bound_to_source_and_workflows()` | 173 | +| function | `test_actual_binary_tamper_fails_digest(tmp_path)` | 178 | +| parameter | `tmp_path` | 178 | +| variable | `root` | 180 | +| variable | `path` | 181 | +| function | `test_renamed_publication_cannot_select_an_external_path(tmp_path)` | 187 | +| parameter | `tmp_path` | 187 | +| variable | `root` | 189 | +| variable | `path` | 190 | +| variable | `record` | 191 | +| function | `test_rehashed_active_office_members_still_fail(tmp_path, member)` | 202 | +| parameter | `member` | 202 | +| parameter | `tmp_path` | 202 | +| variable | `root` | 204 | +| parameter | `entries` | 208 | +| function | `test_rehashed_zip_without_document_root_fails(tmp_path)` | 214 | +| parameter | `tmp_path` | 214 | +| variable | `root` | 216 | +| parameter | `entries` | 217 | +| function | `test_rehashed_workbook_missing_actual_filter_fails(tmp_path)` | 222 | +| parameter | `tmp_path` | 222 | +| variable | `root` | 224 | +| function | `remove_filter(entries)` | 226 | +| parameter | `entries` | 226 | +| variable | `n` | 228 | +| variable | `name` | 228 | +| variable | `table` | 229 | +| function | `test_rehashed_workbook_changed_formula_fails(tmp_path)` | 238 | +| parameter | `tmp_path` | 238 | +| variable | `root` | 240 | +| function | `change_formula(entries)` | 242 | +| parameter | `entries` | 242 | +| variable | `name` | 244 | +| variable | `sheet` | 245 | +| variable | `formula` | 246 | + ## tests/test_release.py | Kind | Declaration | Line | diff --git a/docs/handbook.md b/docs/handbook.md new file mode 100644 index 0000000..2676b7f --- /dev/null +++ b/docs/handbook.md @@ -0,0 +1,67 @@ +# Code Flow Studio handbook + +By Jason Stys. + +Filter diagnostics, unused-code candidates and missed coverage; inspect hash-verified source and navigate safely to unchanged drafts. Fixed rapid typing, worker compatibility and native launch preparation. + +Turns report findings into a reproducible developer triage workflow while preserving source identity and isolated execution. + +## Read the documentation + +| Edition | Audience and contents | +| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | +| [project-guide.docx](reports/project-guide.docx) | User and developer guide: purpose, current usage, design, boundaries and source references. | +| [validation-review.docx](reports/validation-review.docx) | Reviewer edition: exact tested baseline, observed workflows, dated local results and remaining limits. | +| [validation-evidence.xlsx](reports/validation-evidence.xlsx) | Two filterable Excel Tables for observed workflows and test-source inventory, with recalculating outcome formulas. | + +The editable Markdown remains the maintenance source. The Word files are readable editions, and the workbook is an evidence index rather than a test runner. See [publication identities and review](reports/publications.json) and the [documentation refresh record](reports/documentation-refresh-2026-11-09.md). + +## Current usage and help + +[README quick start](../README.md) is the entry point. Use the project-specific guides below before changing inputs, operating modes or deployment assumptions. + +### Usage and operations + +- [release checklist v0.2](release-checklist-v0.2.md) +- [releases/v0.2.0](releases/v0.2.0.md) +- [releases/v0.2.1](releases/v0.2.1.md) +- [releases/v0.3.0](releases/v0.3.0.md) +- [releases/v0.4.0](releases/v0.4.0.md) +- [releases/v0.4.1](releases/v0.4.1.md) + +### Design and contracts + +- [adr polyglot evidence](adr-polyglot-evidence.md) +- [adr private local](adr-private-local.md) +- [architecture](architecture.md) +- [private api](private-api.md) +- [research](research.md) + +### Security and validation + +- [security](security.md) +- [test plan v0.2](test-plan-v0.2.md) +- [testing](testing.md) +- [validation locations](validation-locations.md) +- [validation polyglot](validation-polyglot.md) +- [validation v0.2](validation-v0.2.md) +- [validation](validation.md) + +## Historical results and reproduction + +The reviewed source is [`eeac1df6275437f401a8c060b7770c138dbaf132`](https://github.com/JasonStys/code-flow-studio/tree/eeac1df6275437f401a8c060b7770c138dbaf132). The observed functional workflow baseline is `eeac1df6275437f401a8c060b7770c138dbaf132`; those observations are recorded at `2026-11-10T09:40:54.286353+00:00`. They do not certify an unrun later revision. + +- [Detailed recorded validation](reports/native-registration-completion-2026-11-10.md) +- [Current Actions](https://github.com/JasonStys/code-flow-studio/actions) records later integration checks separately. +- Preserve dated measurements and follow the existing testing guide to reproduce them; a test-source inventory alone does not establish execution. + +## Practical boundaries + +- Twelve bounded flat CLI adapters do not cover every framework, dependency or architecture; coverage and unused-code candidates are not correctness or dead-code proof. +- Execution needs a separately built local Docker worker with no host fallback. Shared worker identity and containers are not a certified public hostile-code sandbox. +- Hashes bind retained bytes and locations, not trusted attestation by a malicious target. UI-only report fixtures are separate from native collector evidence. +- Local accounts trust the OS and unlocked service. Downloads are unsigned development builds; no native mobile installer or clean-machine installer lifecycle certification is claimed. + +## Maintaining these editions + +Update the editable usage/design/test documents first. Rebuild and visually review every Word page and workbook tab before replacing an edition; verify the exact binary digests and evidence source in publications.json. Keep account data, logs with secrets, installed-app state and private test stores out of documentation. Source and release assets retain their existing security and licensing boundaries. diff --git a/docs/reports/documentation-refresh-2026-11-09.md b/docs/reports/documentation-refresh-2026-11-09.md new file mode 100644 index 0000000..3359e06 --- /dev/null +++ b/docs/reports/documentation-refresh-2026-11-09.md @@ -0,0 +1,55 @@ +# Documentation refresh — 2026-11-09 + +By Jason Stys. + +This documentation-only edition describes source `eeac1df6275437f401a8c060b7770c138dbaf132`. It does not change application behavior, accounts, private data, installers or published releases. + +## Published editions and completed review + +| File | Bytes | SHA-256 | Reviewed scope | +| ---------------------------------------------------- | ----: | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | +| [project-guide.docx](project-guide.docx) | 45782 | `50920e113b79dd071f6f1e927d656bfc63d46e1bf98239374de634be2176bfd9` | 6 pages, each viewed individually | +| [validation-review.docx](validation-review.docx) | 48485 | `c94e4fc9b6a56c25baabd7160a5f0414015459107e14ea8c905cdf4c5b9a00ce` | 7 pages, each viewed individually | +| [validation-evidence.xlsx](validation-evidence.xlsx) | 7695 | `12ed9f288f77f4a930115126fdc1b9140b8680da2c0c67d46ff1fc184ee83f8c` | Both tab previews; two actual Excel Tables/autoFilters; four formulas; failure-change/restore recalculation and zero formula errors | + +Word review checked plain black titles, readable pagination, whole table rows, source references, practical limits and absence of clipping or overflow. Render receipts were matched to the current DOCX digest before approval. Workbook review inspected both tab previews and actual exported Table/autoFilter elements; recalculated success/failure summaries were compared independently with workflow metadata. Static previews do not draw Excel dropdown arrows; the exported workbook contains the actual filters. + +## Evidence and change boundary + +- Functional baseline: `eeac1df6275437f401a8c060b7770c138dbaf132`. +- Workflow observation: `2026-11-10T09:40:54.286353+00:00`; exact run URLs and source identities remain in [publications.json](publications.json). +- Local document-package validation verifies exact copied bytes, Office ZIP integrity, approved page/tab identities and internal documentation links. Repository-specific documentation and source-policy commands are run separately and recorded in the integration handoff. +- Source/index metadata is regenerated using the repository’s existing tools. Existing privacy, security, coverage and workflow gates are retained. +- PR and later main workflows must pass independently. This record does not claim future or unrun integration CI success. + +## Interpretation + +The Word files document the cited source; dated source reports retain their original execution scope. A passing workflow is distinct from completed analysis with zero findings, and neither establishes production suitability. The Excel source inventory is not a per-test execution report. Checksums identify exact bytes, not a signature or publisher attestation. + +Use the [handbook](../handbook.md) to choose usage, design, security and test documentation. Keep editable Markdown and reviewed editions together; do not replace historical measurements with undated claims. + +## Local integration checks and future CI + +The complete original controller/training gate passed 261 tests and 28 subtests, +covering 1,462/1,734 measured statements (84.31%, unchanged 80% floor). This adds +nine actual publication cases: exact source-bound reviewed packages, binary tamper, +closed filenames, real macro/ActiveX/embedding ZIP members, missing Word root, +removed Excel autoFilter and changed real formula. Tampered structural fixtures +recompute their archive digest so the structural rejection is exercised independently. +The checks do not execute Office or substitute metadata for the independent visual review. + +XML parsing uses explicitly pinned development-only +[defusedxml 0.7.1](https://pypi.org/project/defusedxml/0.7.1/) with DTDs forbidden; +the [upstream facade](https://github.com/tiran/defusedxml/blob/main/defusedxml/ElementTree.py) +provides entity/external-reference protection. That exact version was already present +in this isolated test environment. No application runtime dependency or account/schema +migration is introduced. ZIP member/expansion budgets, active-member rejection and +actual Table/formula/cache inspection remain independent checks. + +Ruff, 82 exact source maps, strict TypeScript/Vite production build, approved-byte/ZIP/ +source/workflow/link validation and diff hygiene passed. The existing pytest-cache +and large UI-chunk warnings remain visible. These local checks are separate from +the cited eeac full-main observation. The meaningful test-file change triggers the +existing Desktop packages workflow alongside Validate and CodeQL; fresh PR and final +main results must pass before any new draft/download promotion. No runtime, store, +installed application, existing release asset or user account was changed here. diff --git a/docs/reports/native-registration-completion-2026-11-10.md b/docs/reports/native-registration-completion-2026-11-10.md index 808ac3e..8143a92 100644 --- a/docs/reports/native-registration-completion-2026-11-10.md +++ b/docs/reports/native-registration-completion-2026-11-10.md @@ -3,6 +3,49 @@ By Jason Stys. This follow-up changes validation and diagnostics; application behavior, account schemas and the prepared 0.4.1 product version are unchanged. +## Verified current-main source — 2026-11-10 + +Source `eeac1df6275437f401a8c060b7770c138dbaf132` passed all three exact-main +push workflows: [Validate 42791315575](https://github.com/JasonStys/code-flow-studio/actions/runs/42791315575), +[Desktop packages 42791315489](https://github.com/JasonStys/code-flow-studio/actions/runs/42791315489) +and [CodeQL 42791315566](https://github.com/JasonStys/code-flow-studio/actions/runs/42791315566). +The following older failures and local measurements retain their original scopes. + +Actual full-main job logs identify this source and contain no PR diff range or +incremental exclusion filter. They report 52/52 Python, 19/19 TypeScript, 10/10 +JavaScript, 1/1 HTML and 4/4 Actions files scanned. Processed SARIF has zero results +in each category, with 50 Python, 103 JavaScript/TypeScript and 23 Actions queries, +without processing errors/warnings. The main alert inventory is empty. Original +alerts 1–2 automatically closed as fixed; four later PR-only findings have fixed +instances at their own PR refs. No alert was dismissed or query suppressed. +Query counts and scanned-file inventory are not code coverage or vulnerability freedom. + +All three native operating-system jobs passed their SDK and actual compiled +application journeys. The actual Windows job +[129745181301](https://github.com/JasonStys/code-flow-studio/actions/runs/42791315489/job/129745181301) +completed SDK acceptance in 10.3 seconds and compiled acceptance in 7.7 seconds. +Both recorded shutdown HTTP 200/stopping true, actual workspace/lifespan/thread/main +completion and owned frozen-child/bootloader exit; the test also verifies those PIDs +are absent without terminating them. Packaged source identity and the corrected +worker resource SHA are checked against this checkout. This is native package +acceptance, not downloaded-installer or clean-machine proof. + +The complete source Validate gate includes actual isolated workers, privacy/browser +checks and synthetic training smoke; these are separate from static analysis and +local report fixtures. Local controller/training validation measured 1,462/1,734 +statements (84.31%, unchanged 80% floor), with 252 tests and 28 subtests. Five Node +readiness/receipt/descriptor cases and 81 source maps passed. These denominators do +not include worker runtime, trained-model quality or all renderer statements. + +The strengthened completion/diagnostic contracts now provide explicit evidence; +the original intermittent hosted registration/shutdown causes were not reproduced +or conclusively identified. Existing deadlines and zero retries remain unchanged. +A 0.4.1 downloadable release still needs independent draft/download/installer +acceptance and the release owner's promotion; this source observation does not +claim publication or change any installed application, account, store or 0.4.0 asset. + +## Earlier observations and independent regressions + ## Observed hosted failure The full-main source `4f1d0aafd151db3c183e342012900dc75beda96d` passed @@ -31,20 +74,9 @@ values, response bodies, aliases, passwords and recovery/vault material. ## Independent local checks -- Eighteen actual private-browser journeys passed across Chromium, Firefox and - WebKit, including genuine held registration and genuine duplicate-account cases. - The held response comes from the real service: while delivery is held the form - remains busy, with neither recovery nor workspace visible; releasing it completes - a real account/unlock/lock journey. No canned success payload or timed sleep is used. -- The duplicate-account case receives the real conflict response and requires the - helper to reject it before claiming recovery or workspace success. Replacing only - the helper with its previous committed implementation makes this regression fail; - the corrected file was restored byte-for-byte afterwards. -- The owned Windows Electron SDK journey passed in 5.7 seconds. A freshly rebuilt, - unsigned unpacked Windows development application passed in 5.5 seconds, with - embedded reviewed worker bytes and honestly dirty development provenance checked. - Both use disposable profiles and close only their owned process. They do not prove - installation, a clean machine, other operating systems or future hosted results. +- Eighteen actual private-browser journeys passed across Chromium, Firefox and WebKit, including genuine held registration and genuine duplicate-account cases. The held response comes from the real service: while delivery is held the form remains busy, with neither recovery nor workspace visible; releasing it completes a real account/unlock/lock journey. No canned success payload or timed sleep is used. +- The duplicate-account case receives the real conflict response and requires the helper to reject it before claiming recovery or workspace success. Replacing only the helper with its previous committed implementation makes this regression fail; the corrected file was restored byte-for-byte afterwards. +- The owned Windows Electron SDK journey passed in 5.7 seconds. A freshly rebuilt, unsigned unpacked Windows development application passed in 5.5 seconds, with embedded reviewed worker bytes and honestly dirty development provenance checked. Both use disposable profiles and close only their owned process. They do not prove installation, a clean machine, other operating systems or future hosted results. - Strict TypeScript/Vite build, Ruff and generated source-index checks passed. Existing large-chunk and pytest-cache warnings are not hidden. diff --git a/docs/reports/project-guide.docx b/docs/reports/project-guide.docx new file mode 100644 index 0000000000000000000000000000000000000000..67d3b206b5680cb02864ca4571489188ea10b5cf GIT binary patch literal 45782 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 hrWXAM&|?C4k9BW_lg~v2W!ho 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 le(sC@EA3zRpW*$dXR0g@10KD=2.6,<3", "transformers>=4.56,<6", "datasets>=4,<5", "accelerate>=1.10,<2"] [project.scripts] diff --git a/tests/test_publications.py b/tests/test_publications.py new file mode 100644 index 0000000..4251f98 --- /dev/null +++ b/tests/test_publications.py @@ -0,0 +1,252 @@ +# +# Generated definition map; @L is the exact declaration line. +# Function/class docs explain behavior; variables/parameters are scoped inputs, outputs or state. +# functions: validate_publications(root)@L30; copy_publications(tmp_path)@L145; rewrite_package(root, filename, change)@L155; test_actual_reviewed_publications_are_bound_to_source_and_workflows()@L173; test_actual_binary_tamper_fails_digest(tmp_path)@L178; test_renamed_publication_cannot_select_an_external_path(tmp_path)@L187; test_rehashed_active_office_members_still_fail(tmp_path, member)@L202; test_rehashed_zip_without_document_root_fails(tmp_path)@L214; test_rehashed_workbook_missing_actual_filter_fails(tmp_path)@L222; remove_filter(entries)@L226; test_rehashed_workbook_changed_formula_fails(tmp_path)@L238; change_formula(entries)@L242 +# variables: FILES@L26; NS@L27; reports@L32; manifest@L33; source@L34; records@L41; r@L42; workflows@L44; r@L45; r@L56; record@L59; stream@L62; payload@L63; package@L70; members@L71; m@L72; names@L72; required@L73; m@L81; xml@L83; member@L84; lower@L85; marker@L87; text@L97; marker@L98; relation@L102; approval@L110; count@L111; n@L115; name@L119; node@L119; tables@L119; t@L123; cells@L126; node@L128; c@L129; n@L132; row@L133; expected@L134; actual@L140; c@L140; source@L147; target@L148; name@L150; path@L157; original@L158; entries@L159; m@L159; package@L161; content@L162; name@L162; manifest_path@L164; manifest@L165; r@L166; record@L166; payload@L167; root@L180; path@L181; root@L189; path@L190; record@L191; root@L204; root@L216; root@L224; n@L228; name@L228; table@L229; root@L240; name@L244; sheet@L245; formula@L246 +# parameters: root@L30; tmp_path@L145; change@L155; filename@L155; root@L155; tmp_path@L178; tmp_path@L187; member@L202; tmp_path@L202; entries@L208; tmp_path@L214; entries@L217; tmp_path@L222; entries@L226; tmp_path@L238; entries@L242 +# + +"""Jason Stys: source-bound publication integrity with real Office tamper regressions. + +Symbol locations: docs/code-index.md. These checks validate tracked documentation; +they do not run Office, reproduce visual review, authenticate authorship or read accounts. +""" + +import hashlib +import io +import json +import re +import shutil +from pathlib import Path +from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile + +import pytest +from defusedxml import ElementTree as ET + +FILES = {"project-guide.docx", "validation-review.docx", "validation-evidence.xlsx"} +NS = {"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} + + +def validate_publications(root: Path) -> None: + """Require exact reviewed bytes, immutable provenance and bounded inactive Office structures.""" + reports = root / "docs" / "reports" + manifest = json.loads((reports / "publications.json").read_text(encoding="utf-8")) + source = manifest["sourceBaseline"] + if manifest["functionalEvidenceBaseline"] != source: + raise ValueError("Publication functional source identity") + if not re.fullmatch(r"[0-9a-f]{40}", source) or manifest["project"] != "code-flow-studio": + raise ValueError("Publication source identity") + if manifest["sourceUrl"] != "https://github.com/JasonStys/code-flow-studio/tree/" + source: + raise ValueError("Publication source URL") + records = manifest["documents"] + if len(records) != 3 or {r["filename"] for r in records} != FILES: + raise ValueError("Exact named publication set") + workflows = manifest["workflowEvidence"] + if {r["name"] for r in workflows} != { + "Validate", + "CodeQL", + "Desktop packages", + } or len(workflows) != 3: + raise ValueError("Publication workflow set") + if any( + r["headSha"] != source + or r["status"] != "completed" + or r["conclusion"] != "success" + or r["event"] != "push" + for r in workflows + ): + raise ValueError("Publication workflow identity/outcome") + for record in records: + if record["sourceBaseline"] != source or record["visualReview"]["approved"] is not True: + raise ValueError("Publication approval/source") + with (reports / record["filename"]).open("rb") as stream: + payload = stream.read(20 * 1024 * 1024 + 1) + if ( + len(payload) > 20 * 1024 * 1024 + or len(payload) != record["bytes"] + or hashlib.sha256(payload).hexdigest() != record["sha256"] + ): + raise ValueError("Publication digest/size") + with ZipFile(io.BytesIO(payload)) as package: + members = package.infolist() + names = [m.filename for m in members] + required = "xl/workbook.xml" if record["filename"].endswith(".xlsx") else "word/document.xml" + if ( + len(members) > 256 + or len(names) != len(set(names)) + or required not in names + or "[Content_Types].xml" not in names + ): + raise ValueError("Publication ZIP roots/identities") + if sum(m.file_size for m in members) > 32 * 1024 * 1024: + raise ValueError("Publication expanded budget") + xml = {} + for member in members: + lower = "/" + member.filename.replace("\\", "/").lower() + if any( + marker in lower for marker in ["/embeddings/", "/activex/", "vbaproject"] + ) or ".." in member.filename.replace("\\", "/").split("/"): + raise ValueError("Active/embedded publication member") + if ( + member.file_size > 8 * 1024 * 1024 + or member.flag_bits & 1 + or member.compress_type not in {ZIP_STORED, ZIP_DEFLATED} + ): + raise ValueError("Publication member budget/type") + if member.filename.endswith((".xml", ".rels")): + text = package.read(member).decode("utf-8", errors="strict") + if any(marker in text.lower() for marker in [" Path: + """Copy only the four tracked documentation files into a disposable regression fixture.""" + source = Path(__file__).resolve().parents[1] / "docs" / "reports" + target = tmp_path / "docs" / "reports" + target.mkdir(parents=True) + for name in FILES | {"publications.json"}: + shutil.copyfile(source / name, target / name) + return tmp_path + + +def rewrite_package(root: Path, filename: str, change) -> None: + """Make a real changed ZIP and rebind its hash so structural checks, not the digest check, are exercised.""" + path = root / "docs" / "reports" / filename + with ZipFile(path) as original: + entries = {m.filename: original.read(m) for m in original.infolist()} + change(entries) + with ZipFile(path, "w", compression=ZIP_DEFLATED) as package: + for name, content in entries.items(): + package.writestr(name, content) + manifest_path = path.parent / "publications.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + record = next(r for r in manifest["documents"] if r["filename"] == filename) + payload = path.read_bytes() + record["bytes"] = len(payload) + record["sha256"] = hashlib.sha256(payload).hexdigest() + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + + +def test_actual_reviewed_publications_are_bound_to_source_and_workflows(): + """Exercise actual tracked editions, not generated replicas or approval flags alone.""" + validate_publications(Path(__file__).resolve().parents[1]) + + +def test_actual_binary_tamper_fails_digest(tmp_path): + """A changed document cannot keep the original reviewed byte identity.""" + root = copy_publications(tmp_path) + path = root / "docs" / "reports" / "project-guide.docx" + path.write_bytes(path.read_bytes() + b"changed") + with pytest.raises(ValueError, match="digest/size"): + validate_publications(root) + + +def test_renamed_publication_cannot_select_an_external_path(tmp_path): + """Manifest names are closed before any document is opened.""" + root = copy_publications(tmp_path) + path = root / "docs" / "reports" / "publications.json" + record = json.loads(path.read_text(encoding="utf-8")) + record["documents"][0]["filename"] = "../external.docx" + path.write_text(json.dumps(record), encoding="utf-8") + with pytest.raises(ValueError, match="named publication set"): + validate_publications(root) + + +@pytest.mark.parametrize( + "member", + ["word/embeddings/example.bin", "word/activeX/example.xml", "word/vbaProject.bin"], +) +def test_rehashed_active_office_members_still_fail(tmp_path, member): + """A new digest never authorizes opaque embedded objects, ActiveX or macro members.""" + root = copy_publications(tmp_path) + rewrite_package( + root, + "project-guide.docx", + lambda entries: entries.update({member: b"synthetic fixture"}), + ) + with pytest.raises(ValueError, match="Active/embedded"): + validate_publications(root) + + +def test_rehashed_zip_without_document_root_fails(tmp_path): + """A valid ZIP and matching hash do not make a Word document.""" + root = copy_publications(tmp_path) + rewrite_package(root, "project-guide.docx", lambda entries: entries.pop("word/document.xml")) + with pytest.raises(ValueError, match="ZIP roots"): + validate_publications(root) + + +def test_rehashed_workbook_missing_actual_filter_fails(tmp_path): + """Inspect actual exported Table XML; an approval field cannot replace an autoFilter.""" + root = copy_publications(tmp_path) + + def remove_filter(entries): + """Remove the real first Table filter while leaving the rest of the actual workbook intact.""" + name = next(n for n in entries if n.startswith("xl/tables/")) + table = ET.fromstring(entries[name]) + table.remove(table.find("x:autoFilter", NS)) + entries[name] = ET.tostring(table) + + rewrite_package(root, "validation-evidence.xlsx", remove_filter) + with pytest.raises(ValueError, match="Tables/filters"): + validate_publications(root) + + +def test_rehashed_workbook_changed_formula_fails(tmp_path): + """A real changed formula is rejected even when its old cached value and new archive digest agree.""" + root = copy_publications(tmp_path) + + def change_formula(entries): + """Change actual exported XML rather than mocking a formula parser.""" + name = "xl/worksheets/sheet1.xml" + sheet = ET.fromstring(entries[name]) + formula = sheet.find(".//x:f", NS) + formula.text = "SUM(A8:A10)" + entries[name] = ET.tostring(sheet) + + rewrite_package(root, "validation-evidence.xlsx", change_formula) + with pytest.raises(ValueError, match="formula/cached"): + validate_publications(root) From 801f485f57555e0e355536417d8224d2bc1bbbbd Mon Sep 17 00:00:00 2001 From: JasonStys Date: Tue, 10 Nov 2026 04:55:27 -0800 Subject: [PATCH 2/2] Bound actual Office XML inflation and test understated ZIP sizes --- docs/code-index.md | 235 ++++++++++-------- .../documentation-refresh-2026-11-09.md | 8 +- tests/test_publications.py | 78 +++++- 3 files changed, 218 insertions(+), 103 deletions(-) diff --git a/docs/code-index.md b/docs/code-index.md index 242c7cf..cd8e4db 100644 --- a/docs/code-index.md +++ b/docs/code-index.md @@ -2999,105 +2999,146 @@ Generated declaration locations. Python uses AST; JS/TS uses declaration-line ma | Kind | Declaration | Line | |---|---|---| -| variable | `FILES` | 26 | -| variable | `NS` | 27 | -| function | `validate_publications(root)` | 30 | -| parameter | `root` | 30 | -| variable | `reports` | 32 | -| variable | `manifest` | 33 | -| variable | `source` | 34 | -| variable | `records` | 41 | -| variable | `r` | 42 | -| variable | `workflows` | 44 | -| variable | `r` | 45 | -| variable | `r` | 56 | -| variable | `record` | 59 | -| variable | `stream` | 62 | -| variable | `payload` | 63 | -| variable | `package` | 70 | -| variable | `members` | 71 | -| variable | `m` | 72 | -| variable | `names` | 72 | -| variable | `required` | 73 | -| variable | `m` | 81 | -| variable | `xml` | 83 | -| variable | `member` | 84 | -| variable | `lower` | 85 | -| variable | `marker` | 87 | -| variable | `text` | 97 | -| variable | `marker` | 98 | -| variable | `relation` | 102 | -| variable | `approval` | 110 | -| variable | `count` | 111 | -| variable | `n` | 115 | -| variable | `name` | 119 | -| variable | `node` | 119 | -| variable | `tables` | 119 | -| variable | `t` | 123 | -| variable | `cells` | 126 | -| variable | `node` | 128 | -| variable | `c` | 129 | -| variable | `n` | 132 | -| variable | `row` | 133 | -| variable | `expected` | 134 | -| variable | `actual` | 140 | -| variable | `c` | 140 | -| function | `copy_publications(tmp_path)` | 145 | -| parameter | `tmp_path` | 145 | -| variable | `source` | 147 | -| variable | `target` | 148 | -| variable | `name` | 150 | -| function | `rewrite_package(root, filename, change)` | 155 | -| parameter | `change` | 155 | -| parameter | `filename` | 155 | -| parameter | `root` | 155 | -| variable | `path` | 157 | -| variable | `original` | 158 | -| variable | `entries` | 159 | -| variable | `m` | 159 | -| variable | `package` | 161 | -| variable | `content` | 162 | -| variable | `name` | 162 | -| variable | `manifest_path` | 164 | -| variable | `manifest` | 165 | -| variable | `r` | 166 | -| variable | `record` | 166 | -| variable | `payload` | 167 | -| function | `test_actual_reviewed_publications_are_bound_to_source_and_workflows()` | 173 | -| function | `test_actual_binary_tamper_fails_digest(tmp_path)` | 178 | -| parameter | `tmp_path` | 178 | -| variable | `root` | 180 | -| variable | `path` | 181 | -| function | `test_renamed_publication_cannot_select_an_external_path(tmp_path)` | 187 | -| parameter | `tmp_path` | 187 | -| variable | `root` | 189 | -| variable | `path` | 190 | -| variable | `record` | 191 | -| function | `test_rehashed_active_office_members_still_fail(tmp_path, member)` | 202 | -| parameter | `member` | 202 | -| parameter | `tmp_path` | 202 | -| variable | `root` | 204 | -| parameter | `entries` | 208 | -| function | `test_rehashed_zip_without_document_root_fails(tmp_path)` | 214 | -| parameter | `tmp_path` | 214 | -| variable | `root` | 216 | -| parameter | `entries` | 217 | -| function | `test_rehashed_workbook_missing_actual_filter_fails(tmp_path)` | 222 | +| variable | `FILES` | 29 | +| variable | `NS` | 30 | +| variable | `XML_BYTES` | 31 | +| function | `validate_publications(root)` | 34 | +| parameter | `root` | 34 | +| variable | `reports` | 36 | +| variable | `manifest` | 37 | +| variable | `source` | 38 | +| variable | `records` | 45 | +| variable | `r` | 46 | +| variable | `workflows` | 48 | +| variable | `r` | 49 | +| variable | `r` | 60 | +| variable | `record` | 63 | +| variable | `stream` | 66 | +| variable | `payload` | 67 | +| variable | `package` | 74 | +| variable | `members` | 75 | +| variable | `m` | 76 | +| variable | `names` | 76 | +| variable | `required` | 77 | +| variable | `m` | 85 | +| variable | `xml` | 87 | +| variable | `member` | 88 | +| variable | `lower` | 89 | +| variable | `marker` | 91 | +| variable | `xml_stream` | 101 | +| variable | `xml_bytes` | 102 | +| variable | `text` | 105 | +| variable | `marker` | 106 | +| variable | `relation` | 110 | +| variable | `approval` | 118 | +| variable | `count` | 119 | +| variable | `n` | 123 | +| variable | `name` | 127 | +| variable | `node` | 127 | +| variable | `tables` | 127 | +| variable | `t` | 131 | +| variable | `cells` | 134 | +| variable | `node` | 136 | +| variable | `c` | 137 | +| variable | `n` | 140 | +| variable | `row` | 141 | +| variable | `expected` | 142 | +| variable | `actual` | 148 | +| variable | `c` | 148 | +| function | `copy_publications(tmp_path)` | 153 | +| parameter | `tmp_path` | 153 | +| variable | `source` | 155 | +| variable | `target` | 156 | +| variable | `name` | 158 | +| function | `rewrite_package(root, filename, change)` | 163 | +| parameter | `change` | 163 | +| parameter | `filename` | 163 | +| parameter | `root` | 163 | +| variable | `path` | 165 | +| variable | `original` | 166 | +| variable | `entries` | 167 | +| variable | `m` | 167 | +| variable | `package` | 169 | +| variable | `content` | 170 | +| variable | `name` | 170 | +| variable | `manifest_path` | 172 | +| variable | `manifest` | 173 | +| variable | `r` | 174 | +| variable | `record` | 174 | +| variable | `payload` | 175 | +| function | `test_actual_reviewed_publications_are_bound_to_source_and_workflows()` | 181 | +| function | `test_actual_binary_tamper_fails_digest(tmp_path)` | 186 | +| parameter | `tmp_path` | 186 | +| variable | `root` | 188 | +| variable | `path` | 189 | +| function | `test_renamed_publication_cannot_select_an_external_path(tmp_path)` | 195 | +| parameter | `tmp_path` | 195 | +| variable | `root` | 197 | +| variable | `path` | 198 | +| variable | `record` | 199 | +| function | `test_rehashed_active_office_members_still_fail(tmp_path, member)` | 210 | +| parameter | `member` | 210 | +| parameter | `tmp_path` | 210 | +| variable | `root` | 212 | +| parameter | `entries` | 216 | +| function | `test_rehashed_zip_without_document_root_fails(tmp_path)` | 222 | | parameter | `tmp_path` | 222 | | variable | `root` | 224 | -| function | `remove_filter(entries)` | 226 | -| parameter | `entries` | 226 | -| variable | `n` | 228 | -| variable | `name` | 228 | -| variable | `table` | 229 | -| function | `test_rehashed_workbook_changed_formula_fails(tmp_path)` | 238 | -| parameter | `tmp_path` | 238 | -| variable | `root` | 240 | -| function | `change_formula(entries)` | 242 | -| parameter | `entries` | 242 | -| variable | `name` | 244 | -| variable | `sheet` | 245 | -| variable | `formula` | 246 | +| parameter | `entries` | 225 | +| function | `test_rehashed_workbook_missing_actual_filter_fails(tmp_path)` | 230 | +| parameter | `tmp_path` | 230 | +| variable | `root` | 232 | +| function | `remove_filter(entries)` | 234 | +| parameter | `entries` | 234 | +| variable | `n` | 236 | +| variable | `name` | 236 | +| variable | `table` | 237 | +| function | `test_rehashed_workbook_changed_formula_fails(tmp_path)` | 246 | +| parameter | `tmp_path` | 246 | +| variable | `root` | 248 | +| function | `change_formula(entries)` | 250 | +| parameter | `entries` | 250 | +| variable | `name` | 252 | +| variable | `sheet` | 253 | +| variable | `formula` | 254 | +| function | `test_dishonest_compressed_xml_uses_bounded_actual_decompression(tmp_path, monkeypatch)` | 263 | +| parameter | `monkeypatch` | 263 | +| parameter | `tmp_path` | 263 | +| variable | `root` | 265 | +| variable | `name` | 266 | +| parameter | `entries` | 268 | +| variable | `path` | 270 | +| variable | `payload` | 271 | +| variable | `end` | 272 | +| variable | `cursor` | 273 | +| variable | `changed` | 274 | +| variable | `comment_size` | 276 | +| variable | `extra_size` | 276 | +| variable | `filename_size` | 276 | +| variable | `filename` | 277 | +| variable | `local` | 279 | +| variable | `changed` | 283 | +| variable | `cursor` | 284 | +| variable | `manifest_path` | 287 | +| variable | `manifest` | 288 | +| variable | `r` | 289 | +| variable | `record` | 289 | +| variable | `limits` | 292 | +| variable | `factory` | 293 | +| class | `BoundedInflater` | 295 | +| function | `__init__(self, actual)` | 298 | +| parameter | `actual` | 298 | +| parameter | `self` | 298 | +| function | `decompress(self, data, max_length)` | 302 | +| parameter | `data` | 302 | +| parameter | `max_length` | 302 | +| parameter | `self` | 302 | +| function | `__getattr__(self, attribute)` | 309 | +| parameter | `attribute` | 309 | +| parameter | `self` | 309 | +| function | `observed_factory(compression)` | 313 | +| parameter | `compression` | 313 | +| variable | `actual` | 315 | ## tests/test_release.py diff --git a/docs/reports/documentation-refresh-2026-11-09.md b/docs/reports/documentation-refresh-2026-11-09.md index 3359e06..dfa4876 100644 --- a/docs/reports/documentation-refresh-2026-11-09.md +++ b/docs/reports/documentation-refresh-2026-11-09.md @@ -30,12 +30,16 @@ Use the [handbook](../handbook.md) to choose usage, design, security and test do ## Local integration checks and future CI -The complete original controller/training gate passed 261 tests and 28 subtests, +The complete original controller/training gate passed 262 tests and 28 subtests, covering 1,462/1,734 measured statements (84.31%, unchanged 80% floor). This adds -nine actual publication cases: exact source-bound reviewed packages, binary tamper, +ten actual publication cases: exact source-bound reviewed packages, binary tamper, closed filenames, real macro/ActiveX/embedding ZIP members, missing Word root, removed Excel autoFilter and changed real formula. Tampered structural fixtures recompute their archive digest so the structural rejection is exercised independently. +A real deflated ZIP with understated member sizes also checks the actual zlib output +request budget. XML reads explicitly request at most 8 MiB plus one byte before +checking actual length. A sensitivity run restoring the old unbounded read fails +this oracle before inflation; a later CRC rejection alone is not a memory-bound proof. The checks do not execute Office or substitute metadata for the independent visual review. XML parsing uses explicitly pinned development-only diff --git a/tests/test_publications.py b/tests/test_publications.py index 4251f98..470680c 100644 --- a/tests/test_publications.py +++ b/tests/test_publications.py @@ -1,9 +1,10 @@ # # Generated definition map; @L is the exact declaration line. # Function/class docs explain behavior; variables/parameters are scoped inputs, outputs or state. -# functions: validate_publications(root)@L30; copy_publications(tmp_path)@L145; rewrite_package(root, filename, change)@L155; test_actual_reviewed_publications_are_bound_to_source_and_workflows()@L173; test_actual_binary_tamper_fails_digest(tmp_path)@L178; test_renamed_publication_cannot_select_an_external_path(tmp_path)@L187; test_rehashed_active_office_members_still_fail(tmp_path, member)@L202; test_rehashed_zip_without_document_root_fails(tmp_path)@L214; test_rehashed_workbook_missing_actual_filter_fails(tmp_path)@L222; remove_filter(entries)@L226; test_rehashed_workbook_changed_formula_fails(tmp_path)@L238; change_formula(entries)@L242 -# variables: FILES@L26; NS@L27; reports@L32; manifest@L33; source@L34; records@L41; r@L42; workflows@L44; r@L45; r@L56; record@L59; stream@L62; payload@L63; package@L70; members@L71; m@L72; names@L72; required@L73; m@L81; xml@L83; member@L84; lower@L85; marker@L87; text@L97; marker@L98; relation@L102; approval@L110; count@L111; n@L115; name@L119; node@L119; tables@L119; t@L123; cells@L126; node@L128; c@L129; n@L132; row@L133; expected@L134; actual@L140; c@L140; source@L147; target@L148; name@L150; path@L157; original@L158; entries@L159; m@L159; package@L161; content@L162; name@L162; manifest_path@L164; manifest@L165; r@L166; record@L166; payload@L167; root@L180; path@L181; root@L189; path@L190; record@L191; root@L204; root@L216; root@L224; n@L228; name@L228; table@L229; root@L240; name@L244; sheet@L245; formula@L246 -# parameters: root@L30; tmp_path@L145; change@L155; filename@L155; root@L155; tmp_path@L178; tmp_path@L187; member@L202; tmp_path@L202; entries@L208; tmp_path@L214; entries@L217; tmp_path@L222; entries@L226; tmp_path@L238; entries@L242 +# classes: BoundedInflater@L295 +# functions: validate_publications(root)@L34; copy_publications(tmp_path)@L153; rewrite_package(root, filename, change)@L163; test_actual_reviewed_publications_are_bound_to_source_and_workflows()@L181; test_actual_binary_tamper_fails_digest(tmp_path)@L186; test_renamed_publication_cannot_select_an_external_path(tmp_path)@L195; test_rehashed_active_office_members_still_fail(tmp_path, member)@L210; test_rehashed_zip_without_document_root_fails(tmp_path)@L222; test_rehashed_workbook_missing_actual_filter_fails(tmp_path)@L230; remove_filter(entries)@L234; test_rehashed_workbook_changed_formula_fails(tmp_path)@L246; change_formula(entries)@L250; test_dishonest_compressed_xml_uses_bounded_actual_decompression(tmp_path, monkeypatch)@L263; __init__(self, actual)@L298; decompress(self, data, max_length)@L302; __getattr__(self, attribute)@L309; observed_factory(compression)@L313 +# variables: FILES@L29; NS@L30; XML_BYTES@L31; reports@L36; manifest@L37; source@L38; records@L45; r@L46; workflows@L48; r@L49; r@L60; record@L63; stream@L66; payload@L67; package@L74; members@L75; m@L76; names@L76; required@L77; m@L85; xml@L87; member@L88; lower@L89; marker@L91; xml_stream@L101; xml_bytes@L102; text@L105; marker@L106; relation@L110; approval@L118; count@L119; n@L123; name@L127; node@L127; tables@L127; t@L131; cells@L134; node@L136; c@L137; n@L140; row@L141; expected@L142; actual@L148; c@L148; source@L155; target@L156; name@L158; path@L165; original@L166; entries@L167; m@L167; package@L169; content@L170; name@L170; manifest_path@L172; manifest@L173; r@L174; record@L174; payload@L175; root@L188; path@L189; root@L197; path@L198; record@L199; root@L212; root@L224; root@L232; n@L236; name@L236; table@L237; root@L248; name@L252; sheet@L253; formula@L254; root@L265; name@L266; path@L270; payload@L271; end@L272; cursor@L273; changed@L274; comment_size@L276; extra_size@L276; filename_size@L276; filename@L277; local@L279; changed@L283; cursor@L284; manifest_path@L287; manifest@L288; r@L289; record@L289; limits@L292; factory@L293; actual@L315 +# parameters: root@L34; tmp_path@L153; change@L163; filename@L163; root@L163; tmp_path@L186; tmp_path@L195; member@L210; tmp_path@L210; entries@L216; tmp_path@L222; entries@L225; tmp_path@L230; entries@L234; tmp_path@L246; entries@L250; monkeypatch@L263; tmp_path@L263; entries@L268; actual@L298; self@L298; data@L302; max_length@L302; self@L302; attribute@L309; self@L309; compression@L313 # """Jason Stys: source-bound publication integrity with real Office tamper regressions. @@ -17,6 +18,8 @@ import json import re import shutil +import struct +import zipfile from pathlib import Path from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile @@ -25,6 +28,7 @@ FILES = {"project-guide.docx", "validation-review.docx", "validation-evidence.xlsx"} NS = {"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} +XML_BYTES = 8 * 1024 * 1024 def validate_publications(root: Path) -> None: @@ -94,7 +98,11 @@ def validate_publications(root: Path) -> None: ): raise ValueError("Publication member budget/type") if member.filename.endswith((".xml", ".rels")): - text = package.read(member).decode("utf-8", errors="strict") + with package.open(member) as xml_stream: + xml_bytes = xml_stream.read(XML_BYTES + 1) + if len(xml_bytes) > XML_BYTES or len(xml_bytes) != member.file_size: + raise ValueError("Publication actual XML budget/length") + text = xml_bytes.decode("utf-8", errors="strict") if any(marker in text.lower() for marker in ["" + b"x" * 65536 + b""}) + ) + path = root / "docs" / "reports" / "project-guide.docx" + payload = bytearray(path.read_bytes()) + end = payload.rfind(b"PK\x05\x06") + cursor = struct.unpack_from("