diff --git a/.gitignore b/.gitignore index 31591d72..20e29de4 100644 --- a/.gitignore +++ b/.gitignore @@ -91,20 +91,24 @@ _transfer/ # Local developer scratch notes and chat handoffs - never committed devnotes/ -# docs/ is generated/published to the wiki, not tracked in the product repo. -# A single tracked file here makes docs/ exist in the CI checkout, which -# defeats the intentional skip in the docs publish/version tests. Keep this -# local-only guide out of version control so those tests skip as designed. -docs/CSV-IMPORT-GUIDE.md +# docs/ prose is published to the wiki, not tracked in the product repo. The +# docs gates detect a published checkout by the ABSENCE of docs/*.md, so a +# single tracked prose file here makes CI look like an internal checkout while +# carrying almost no prose: test_there_are_docs_to_check then fails asserting +# >20 files, test_the_map_itself_is_current fails on a generated PROJECT-MAP +# that is correctly untracked, and check-gates-ran.sh fails on the resulting +# skip. That happened - DEV-BUILD-TEST.md and PLUGIN-HOOKS.md were committed in +# unrelated changes and broke the branch for days. Ignore the whole pattern +# rather than naming files one at a time, so the next authored doc cannot +# reintroduce it. +# +# Scoped to the top level on purpose: docs/notes/ prose stays tracked, and the +# gates only glob docs/*.md. docs/openapi.json is tracked (the app serves it) +# and is unaffected by a *.md rule. +docs/*.md -# Local-only authored docs (executive summaries, workflow/security writeups, -# planner exports, draft decks and diagrams). Same reasoning as above: keep -# personal docs out of version control. docs/openapi.json stays tracked and -# is intentionally NOT ignored here. -docs/EXECUTIVE-SUMMARY.md -docs/DESIGN-DECISIONS-agent.md -docs/OPERATOR-WORKFLOW.md -docs/SECURITY-RISK-ASSESSMENT.md +# Local-only authored docs (planner exports, draft decks and diagrams). Same +# reasoning as above: keep personal docs out of version control. docs/ShopDB-Planner-Tasks.csv docs/DRAFT--ShopDB-CSV-Import-Guide.pptx docs/diagrams/ diff --git a/docs/DEV-BUILD-TEST.md b/docs/DEV-BUILD-TEST.md deleted file mode 100644 index ac357d1b..00000000 --- a/docs/DEV-BUILD-TEST.md +++ /dev/null @@ -1,45 +0,0 @@ -# ShopDB dev build - quick test - -Two dev builds you can stand up on a Windows box with Docker Desktop. You run -one PowerShell script: it checks your dependencies (offers to winget-install -anything missing), then PULLS a prebuilt image from GHCR and starts the stack in -Docker. Nothing is compiled on your machine - no Node, no npm, no source build - -so it works on a box that has only Docker installed. - -## What you need - -- Windows with Docker Desktop running. -- Git for Windows (`winget install Git.Git`). -- A GitHub login for this private repo/registry already stored on the box. If - you have cloned this repo before, or signed in to GitHub in Visual Studio or - VS Code, you already have it - the script uses that stored credential - automatically. If Docker cannot pull the image, sign in once with - `docker login ghcr.io` (use a GitHub username and a personal access token that - has `read:packages`), then re-run the launcher. - -## Get the launcher - -You will be sent `Start-ShopDBDev.ps1` directly. Save it anywhere (for example -your Downloads folder) and open PowerShell in that folder. - -## Bleeding edge (dev-be) - - powershell -ExecutionPolicy Bypass -File .\Start-ShopDBDev.ps1 -Channel edge - -Comes up at https://localhost:5011. Red DEV badge in the sidebar. Pulls the -`edge` image. - -## Stable (main) - - powershell -ExecutionPolicy Bypass -File .\Start-ShopDBDev.ps1 -Channel stable - -Comes up at https://localhost:5001. No DEV badge. Pulls the `stable` image. - -## Notes - -- Both stacks can run at the same time - they use different ports and separate - Docker volumes, so they do not collide and each keeps its own database. -- Each launch pulls the latest image for the channel, so re-running the launcher - is how you pick up a new build. No local rebuild step. -- First page load shows a self-signed cert warning (expected for a dev box). -- Stop a stack: `docker compose -p shopdb-stable down` (or `shopdb-edge`). diff --git a/docs/PLUGIN-HOOKS.md b/docs/PLUGIN-HOOKS.md deleted file mode 100644 index a4b3f531..00000000 --- a/docs/PLUGIN-HOOKS.md +++ /dev/null @@ -1,45 +0,0 @@ -# Plugin hooks and contract surface - -Everything a plugin may import from the core lives in `shopdb.api` (ADR-001), -versioned by `__contract_version__` (ADR-002). Adding a name is a minor bump; -removing one is major. This file records the non-obvious hooks - the ones a -plugin author would not guess from the name alone. It is not the full export -list; read `shopdb/api/__init__.py` for that. - -## Reversible bulk-import journal - -Contract version 0.30.0. - -A plugin that drives a bulk import - a site asset adoption, an integration sync - -records the primitive changes it makes so the whole batch can be reversed later. -The journal is import-agnostic: it stores primitive mutations (set a cell, insert -a row) tied to a batch id, and a generic undo replays them in reverse. Nothing in -core learns what the import means. - -Exposed on `shopdb.api`: - -- `ImportBatch` - one row per applied import. `batchid` is the identity that the - approval step, the write, and the undo all reference. `status` is `applied` or - `undone`. -- `ImportJournalEntry` - one reversible primitive mutation. `operation` is - `update` (a cell change, carrying `oldvalue`/`newvalue` so it can be put back) - or `insert` (a row this batch created, so undo can delete strictly what the - import created). -- `BATCH_APPLIED`, `BATCH_UNDONE` - the two `ImportBatch.status` values. -- `OP_UPDATE`, `OP_INSERT` - the two `ImportJournalEntry.operation` values. - -What a plugin gets from this, and what it does NOT: - -- A plugin's import UI reads `ImportBatch` to list applied batches (kind, row - count, who applied it, when) and offers an "Undo import" per batch. -- A plugin does NOT write the journal or run undo through these model imports. - The write/undo logic lives with the import writer (see - `scripts/site_imports/journal.py`), which records entries in the SAME - transaction as the mutation they describe and reverses them conservatively: - an `update` is put back only if the cell still holds the value the import - wrote (a value a human changed since is left as-is and reported), and an - `insert` is hard-deleted only when nothing outside the batch still references - the created row. - -The tables are core-owned (migration `7d37_import_batches_and_journal`), so they -exist regardless of which plugins are installed. diff --git a/frontend/src/components/pinnedPanels.spec.js b/frontend/src/components/pinnedPanels.spec.js index f5ac0499..bf1ee53c 100644 --- a/frontend/src/components/pinnedPanels.spec.js +++ b/frontend/src/components/pinnedPanels.spec.js @@ -88,9 +88,14 @@ describe('the header cloud identity block', () => { it('marks the templates settled even when the fetch fails', () => { // Otherwise a down settings backend hides the block permanently. + // Bounded by the function's own closing brace rather than a character + // count: a fixed window silently excluded the finally block as soon as + // the retry logic grew, which reported as "the finally is missing" when + // the real code was correct. Slice to the next top-level declaration. const source = pcDetailSource() - const start = source.indexOf('getEntraDeviceUrls()') - const block = source.slice(start, start + 420) + const start = source.indexOf('async function loadPortalTemplates') + const next = source.indexOf('\nonUnmounted(', start) + const block = source.slice(start, next === -1 ? undefined : next) expect(block).toContain('finally') expect(block).toContain('portalTemplatesLoaded.value = true') }) diff --git a/frontend/src/utils/siteSettings.spec.js b/frontend/src/utils/siteSettings.spec.js index 7174329d..bb5fa838 100644 --- a/frontend/src/utils/siteSettings.spec.js +++ b/frontend/src/utils/siteSettings.spec.js @@ -70,6 +70,8 @@ describe('loading', () => { expect(await settings.getEntraDeviceUrls()).toEqual({ entraUrl: 'https://entra/{id}', intuneUrl: 'https://intune/{id}', + lapsUrl: '', + loaded: true, }) expect(list).toHaveBeenCalledTimes(1) }) @@ -82,7 +84,11 @@ describe('failure', () => { const settings = await freshModule() expect(await settings.getFacilityName()).toBe('ShopDB') - expect(await settings.getEntraDeviceUrls()).toEqual({ entraUrl: '', intuneUrl: '' }) + // loaded:false is the point - a failed fetch and an unconfigured site both + // read as empty strings, and the caller needs to tell them apart. + expect(await settings.getEntraDeviceUrls()).toEqual({ + entraUrl: '', intuneUrl: '', lapsUrl: '', loaded: false, + }) }) it('does not hammer a backend that is down', async () => { diff --git a/tests/test_docs_publishable.py b/tests/test_docs_publishable.py index 2598a99f..c4256873 100644 --- a/tests/test_docs_publishable.py +++ b/tests/test_docs_publishable.py @@ -94,7 +94,23 @@ def documentation_files(): def test_there_are_docs_to_check(): """A path change that silently matched nothing would make this suite pass - while checking absolutely nothing.""" + while checking absolutely nothing. + + FLAG FOR DOC AUTHORS: if this fails with a SMALL number (3, not 0), you have + almost certainly just committed a docs/*.md file. Don't lower the threshold + to make it pass - that is the exact failure this assertion exists to catch, + and it would silently disable the whole scrub suite. + + docs/*.md prose is wiki-published and MUST stay untracked. The gates decide + 'is this a published checkout?' by the ABSENCE of docs/*.md, so one tracked + prose file makes CI look internal while carrying no prose, and takes down + backend AND gates together. docs/DEV-BUILD-TEST.md and docs/PLUGIN-HOOKS.md + did exactly this and held the branch red for eight runs; both were untracked + (kept on disk) and .gitignore now ignores docs/*.md wholesale. + + Authoring a doc is still fine - write it in docs/, it just stays local and + goes to the wiki. docs/notes/ and docs/openapi.json are unaffected. + """ assert len(documentation_files()) > 20 diff --git a/tests/test_docs_versions.py b/tests/test_docs_versions.py index 4b001c01..e743eac8 100644 --- a/tests/test_docs_versions.py +++ b/tests/test_docs_versions.py @@ -121,7 +121,14 @@ def test_a_stated_plugin_count_matches_the_tree(): def test_the_map_itself_is_current(): """The pointer target has to be right, or every document pointing at it is - wrong at one remove.""" + wrong at one remove. + + FLAG FOR DOC AUTHORS: if this fails saying PROJECT-MAP.md is missing, the + likely cause is not a missing map - it is that you committed a docs/*.md + file, which stopped this module from skipping as a published checkout. The + map is generated and correctly untracked. See the note in + tests/test_docs_publishable.py::test_there_are_docs_to_check. + """ mapfile = DOCS / 'PROJECT-MAP.md' assert mapfile.is_file(), 'the PROJECT-MAP wiki page is missing; run scripts/gen_project_map.py' text = mapfile.read_text()