Modum production search implementation plan
This plan does not authorize deployment or production changes. Implementation steps use checkboxes for tracking.
Goal: Deliver reliable semantic and keyword search within Modum's existing website, with complete coverage of the approved public corpus, controlled synchronization, an operational owner and a tested rollback.
Architecture: SiteVision remains the CMS. A signed RESTApp exports approved, anonymously readable published content and sends change events to a BCW-operated service. That service extracts documents, maintains an independent search index and serves results through a signed SiteVision WebApp on /sok.
Tech stack: Existing Next.js/TypeScript service, Node.js 22.13.0 or later, SQLite, Gemini embeddings, optional Mistral OCR, SiteVision RESTApp 2 and WebApp 2. The current application uses Next.js 16.3.7. Model providers remain subject to Modum's production decision.
Spec: Semantic search for Modum kommune. Implementation baseline: docs/implementation-progress.md and docs/sitevision-setup.md. This plan incorporates the authenticated inspection on 29 September 2026 and supersedes those documents' outdated access assumptions.
Outcome and boundaries
The first release returns source links and excerpts, not generated answers. It covers approved SiteVision pages, articles, PDF, DOCX and DOC files. It preserves /sok?query=..., the homepage and header search entry points, suggestions, filters, pagination and a working original-search fallback.
External systems such as public records, maps, job listings, fault reporting and waste collection do not become searchable merely because SiteVision links to them. Initially, use approved service links where appropriate. Indexing their underlying records requires a separate source agreement, connector and acceptance exercise.
The work ends when production is operating under an agreed support arrangement, Modum has accepted coverage and relevance, and rollback and recovery have been demonstrated. A running local prototype or an uploaded ZIP is not completion.
Global constraints
- Preserve Modum's CMS, URLs and editorial workflow.
- Keep
querylinks and the public/sokroute compatible. - Export only approved content with both current online status and anonymous read permission. An editor's ability to read content is not proof of public eligibility.
- Respect indexing exclusions and protected files; exclude drafts, user records, submitted forms and editor-only content.
- Separate deployment credentials, connector credentials, event-receiver credentials, operator credentials and model credentials. No browser secrets.
- SiteVision's verified build is 2026.08.1.3, in se48 Production. The extraction API's documented minimum is 2026.03.1. Runtime compatibility still needs testing.
- Production explicitly prohibits unsigned apps. Do not disable this protection.
- Use distinct staging and production indexes, secrets and configuration. Do not mirror private production content into test systems.
- Retain source identity independently of URLs and preserve
sourceLocaleseparately from normalized language. - Fail closed when public eligibility expires. Neither cached suggestions nor old crawl records may bypass a revocation.
- No writes to Modum, package uploads, trust changes, publishing or paid infrastructure provisioning as part of this planning task.
- Preserve existing uncommitted work, especially the pre-existing
app/page.tsxchange. Stage only reviewed files during implementation.
Verified baseline and evidence limits
| Area | Evidence | Consequence |
|---|---|---|
| Account | Sebastian Mandal, Bluecolorworks, authenticated through the Bluecolorworks Chrome profile | Interactive access exists; unattended credentials have not been established |
| Content and templates | Existing page and template editors expose editing and publishing controls | We can prepare the integration without requesting general editorial access again |
| Files/images | Both archives expose upload and creation controls | The relevant archive management UI is available |
| Add-ons | Import, custom RESTApp management, properties and restrictions panels open | Installation and configuration controls are available; no upload was attempted |
| Model REST | Enabled; access restricted to Automatisering and Bluecolorworks | Group access is configured; an actual authenticated Model REST read and the tenant API contract remain to be tested |
| Service users | Management panel opens and offers creation | Relevant administration control exists; no new service account is currently required by the anonymous exporter design |
| General permissions | Users, roles and page-permissions management panels are absent | Do not claim unrestricted administration or attempt to work around these limits |
| Signing | Existing unsigned custom app reports that unsigned apps/modules are prohibited | Both BCW packages require valid signing and applicable trust |
| Certificate trust | Website's additional trusted developer certificate list is empty | BCW trust has not been established; this does not rule out server-wide trust for other publishers |
| Environment | Cloud panel identifies se48 Production | No staging environment has been verified |
| Hosting | Local service and worker only | Production hosting, DNS, backups and supervision are still needed |
This was a read-only inspection. Available controls establish UI access, not successful writes to every page or successful execution of our packages. A production signing certificate belongs to a developer team, not to an ordinary CMS login. SiteVision documents team membership, signing and website trust in its certificate workflow.
Already built locally, according to the implementation record:
- Hybrid search API, local Norwegian UI, filters, suggestions, signed cursors and keyword fallback.
- Public crawler and PDF/DOCX/DOC extraction, with optional OCR.
- SQLite index, durable event receiver, suppression, retry and reconciliation mechanisms.
- RESTApp and WebApp packages that build but have never run on Modum's tenant.
- A last recorded corpus of 1,118 documents and 9,284 chunks. These are a crawl snapshot, not an approved or complete editorial inventory.
- A last recorded result of 32 root tests and two WebApp rendering tests passing. Re-run at implementation baseline.
- A provisional evaluation of 50/56 hybrid queries versus 44/56 keyword queries with an expected URL in the first five. These are engineering judgments, not Modum acceptance.
Outside intervention and exactly when it is needed
Start the requests in rows A–D together once implementation is authorized. Local engineering can proceed while responses arrive. Do not send requests to anyone merely because this plan names them.
| Gate | Owner outside the implementation agent | Required input or action | Blocks | Work that continues while waiting |
|---|---|---|---|---|
| A. Signing | BCW's SiteVision developer-team administrator, or Modum's existing SiteVision partner; SiteVision if no team exists | Confirm developer-team membership and a permitted signing workflow; provide the public signing certificate and a way to sign both packages without sharing private credentials in chat | Installing/running our apps on production; staging too unless it has a suitable developer licence | Connector tests, UI completion, hosting preparation, operational work |
| B. Test environment | Modum's website owner or SiteVision hosting partner | Supply an isolated staging site, account access and approved public fixtures; confirm its API/build/extension support | Tests that publish, unpublish, restrict, move, trash and replace content | Mocked tests and external-service staging |
| C. Infrastructure | Sebastian/BCW infrastructure owner | Select hosting account, region, budget, domain and operator; grant deployment, secret-store and DNS access | Managed HTTPS deployment, backups, monitoring and live event delivery | Container hardening and deployment/runbook preparation |
| D. Content and processing | Modum content owner and the person responsible for its supplier/data-processing decisions | Approve source roots, document categories, exclusions, model/OCR providers and regions, query-log retention, freshness and availability expectations | Production indexing scope and production provider use | Public fixture work and provisional relevance tuning within existing authorization |
| E. Missing CMS rights | Modum/SiteVision administrator, only if an actual operation is denied | Perform the specific blocked action or grant the smallest necessary permission | Only the denied operation | Every independent task; do not request global admin pre-emptively |
| F. Trust and API exposure | Sebastian plus Modum's authorized site owner | Approve the specific certificate to trust and the connector's anonymous GET surface protected by its application key; approve any new unattended account if subsequently required | Applying certificate trust or materially expanding access | Signed-package preparation and review of exact settings |
| G. Relevance and acceptance | Modum service/content representatives | Judge 50–100 resident tasks, resolve excluded/failed content, approve accessibility and critical service results | Public launch | Performance, recovery and integration tests |
| H. Release | Modum release owner and Sebastian/BCW operator | Approve the exact packages, /sok switch, observation window and rollback owner | Production activation and publication | All preparation and rehearsal |
| I. Additional sources | Each external-system owner/vendor | API documentation, approved data scope, credentials and usage terms | Searching records held outside SiteVision | Core SiteVision release; external connectors are separate scope |
No additional API key is required just to finish local engineering. Working development Gemini and Mistral credentials already exist in ignored local configuration. Production needs approved billing ownership and dedicated quotas/credentials, or explicit approval to use the current ones. Previously tested Azure embedding credentials failed authentication; obtain replacements only if Azure is selected. A normal SiteVision login does not supply the signing credential or the connector key.
If staging cannot be supplied, stop before mutation-based integration tests. An agreed production pilot restricted to designated test content is a separate decision, not an automatic substitute. If signing is delayed, the existing public-crawl prototype can remain a demonstration; it must not be relabeled as the completed CMS integration. Direct Model REST polling is a fallback investigation, not a reason to abandon the built connector without evidence.
Delivery sequence and gates
- Establish a reproducible baseline and request the external inputs.
- Finalize the source contract and production targets with Modum.
- Harden the connector, synchronization, storage, API and SiteVision UI locally.
- Provision external staging and obtain signed packages and CMS staging access.
- Validate extraction and change propagation on representative content.
- Expand to the full approved corpus; tune relevance and complete load/accessibility/recovery checks.
- Obtain acceptance, rehearse rollback, install production versions and switch
/sok. - Observe the release and transfer operational responsibility.
Signing and CMS staging are the main external critical path. Do not serialize local UI/API work behind them. Hosting can also be prepared independently. No infrastructure migration is automatic: retain single-host SQLite if it meets the approved targets; change storage only on measured need.
Review focus
- A parent becomes private while descendant extraction is in flight. Search, suggestions, caches and old crawl copies must all stop exposing the descendant. Owned by Tasks 3, 4 and 7.
- Full-corpus refresh exceeds the five-minute lease. Content must not disappear repeatedly during normal operation, and slow files must not block eligibility checks. Owned by Task 4.
- Routine lease renewals invalidate pagination. Next-page navigation must remain usable while current access checks still remove revoked results. Owned by Tasks 4 and 5.
- External waste-collection questions and unrelated queries look semantically similar to old documents. The service must show useful approved destinations or a clear no-result state. Owned by Task 7.
- A model provider fails, SiteVision times out, or an index is restored from backup. Public UI must provide the documented fallback and must not expose content whose eligibility has expired. Owned by Tasks 5, 6, 8 and 9.
File and interface map
All paths below are relative to the semantic-modum application repository.
| Area | Existing files to use/modify | Planned additions and responsibility |
|---|---|---|
| Source policy | lib/search/ingestion.ts, lib/search/types.ts, integrations/sitevision/src/connector.js | docs/source-contract.md, lib/search/source-policy.ts, tests/source-policy.test.ts: exact origins, exclusions and environment boundaries |
| Connector/export/events | integrations/sitevision/src/index.js, src/connector.js, src/config/index.html, manifest.json, package.json | src/policy.js, src/outbox.js, test/policy.test.js, test/outbox.test.js: independently testable eligibility and delivery decisions |
| Synchronization | scripts/sync-worker.ts, lib/search/sync.ts, lib/search/store.ts, tests/sync.test.ts, tests/store.test.ts | lib/search/refresh.ts, tests/refresh.test.ts: eligibility scheduling independent of extraction and embedding |
| API/retrieval | lib/search/api.ts, lib/search/ranking.ts, lib/search/embeddings.ts, tests/api.test.ts, tests/search.test.ts | lib/search/query-cache.ts, tests/query-cache.test.ts: bounded query-vector reuse, separate from access checks |
| CMS search UI | integrations/sitevision-search/src/index.js, src/view.js, src/config/index.html, test/view.test.js | src/main.js and scoped assets if progressive suggestions need client code; test/requests.test.js for request/timeout/error contracts |
| Operations | Dockerfile, compose.yaml, .env.example, app/v1/health/route.ts, app/v1/admin/sync/status/route.ts | scripts/backup-search.ts, scripts/restore-search.ts, scripts/load-search.ts, tests/operations.test.ts, docs/operations.md |
| Evaluation/release | scripts/evaluate-search.ts, README.md, docs/sitevision-setup.md, docs/implementation-progress.md | evaluation/resident-tasks.json, docs/acceptance.md, docs/release-runbook.md, docs/access-audit.md |
Preserve these existing interfaces:
GET /v1/search?query=...&limit=10&type=...&language=...&cursor=...
GET /v1/suggest?query=...&limit=5
POST /v1/integrations/sitevision/events Authorization: Bearer SEARCH_SYNC_TOKEN
GET /v1/admin/sync/status Authorization: Bearer SEARCH_ADMIN_TOKEN
POST /v1/admin/sync/reconcile Authorization: Bearer SEARCH_ADMIN_TOKEN
GET /v1/health
GET /rest-api/modum-search-connector/manifest?cursor=...
GET /rest-api/modum-search-connector/documents/:sourceId
X-BCW-Connector-Key: SITEVISION_CONNECTOR_KEY
lib/search/types.ts remains the canonical definition of SourceDocument, IndexedDocument and SearchResponse. Preserve items, nextCursor, total.value, total.relation, mode, requestId and degraded. Add contracts explicitly in their owning task; do not let the CMS module silently diverge from the service.
Task 1: Establish the baseline and access record
Owner: BCW implementation. Dependencies: none. Outside intervention: none for this task.
Files: docs/access-audit.md, docs/implementation-progress.md, docs/sitevision-setup.md, existing test/build configuration.
- Record the inspected production build, role label, available panels and signing error. Distinguish observed controls from write-tested permissions. Update stale claims that tenant access and version are unknown.
- Record the current branch, working-tree changes and package versions. Preserve the user's existing edits; take a safe source baseline before further implementation.
- Run the existing verification commands below. Diagnose actual failures before feature changes; do not treat earlier passing results as current evidence.
- Confirm both unsigned build artifacts are reproducible. Record their hashes locally; do not upload them.
- Review and commit only the baseline/documentation changes that belong to this task.
pnpm test
pnpm exec tsc --noEmit
pnpm lint
pnpm build
npm --prefix integrations/sitevision ci
npm --prefix integrations/sitevision run build
npm --prefix integrations/sitevision-search ci
npm --prefix integrations/sitevision-search test
npm --prefix integrations/sitevision-search run build
Exit: A reproducible baseline and an accurate access record. Full lint may reveal pre-existing issues; record them separately and resolve any issue that affects release.
Task 2: Finalize the public source and environment contract
Owner: BCW, with Modum's content owner. Dependencies: Task 1. Outside intervention: Gates B and D before tenant tests/full ingestion.
Files: docs/source-contract.md, lib/search/source-policy.ts, lib/search/ingestion.ts, lib/search/types.ts, tests/source-policy.test.ts, connector and WebApp configuration files.
- Read the tenant's authenticated
/rest-api/docthrough the authorized session; verify one permitted read. Record response shapes for online nodes/properties and pagination. Do not export cookies or reuse browser session secrets in the worker. - Record approved page, archive, global-file and local-file repository IDs. The observed site ID is not a sufficient approval to export every descendant.
- Resolve canonical origin explicitly. Chrome displayed
modum.kommune.no, while current service and renderer allow onlywww.modum.kommune.no. Read actual online canonical URLs and configure the accepted origin/redirect policy without accepting arbitrary hosts. - Parameterize staging origins in one source-policy module. Test HTTPS-only, exact host/port matching, rejection of userinfo and off-origin redirects, admin/login/offline routes and forbidden file types. Keep staging and production indexes physically separate.
- Use synthetic public fixtures on staging. Do not make the entire staging website public merely to satisfy the anonymous exporter.
- Audit 30–50 representative pages/files, including linked layouts, accordions, tables, archives, reusable content, custom modules and local files. Compare anonymous visible content, indexable extraction and headless output.
- Record accepted file sizes, OCR handling, document-title precedence, versions and external-link policy. Every omitted item needs a reason, rather than silent loss.
- Decide production model/OCR providers, retention and support targets with Modum. Inspect native semantic search entitlement and compare on the same tasks if available; an installed Assistant module does not prove entitlement or coverage.
Proposed targets for agreement, not existing guarantees: five-minute maximum eligibility lease; revocation visible within 60 seconds after receiver acknowledgement and no later than lease expiry when events fail; normal publication searchable within five minutes; API p95 at most one second and rendered results p95 at most two seconds at agreed peak traffic. Initially assume ten concurrent search users for a smoke load, then replace that assumption with measured/approved traffic before sizing production. No raw query logging by default.
Exit: Modum-approved source inventory and operational targets, plus a documented extraction method for each fixture. Provider changes trigger a separately versioned reindex.
Task 3: Harden and sign the SiteVision connector
Owner: BCW. Dependencies: Task 2 contract; signing can proceed alongside local work. Outside intervention: Gates A, B and F for signing, trust and installation.
Files: integrations/sitevision/src/connector.js, src/index.js, src/policy.js, src/outbox.js, src/config/index.html, manifest.json, package.json, test/policy.test.js, test/outbox.test.js.
- Add a test script using Node's test runner and extract pure policy/outbox decisions behind injected SiteVision adapters. Write failing tests before changing eligibility or delivery behavior.
- Test unpublished pages, private pages, inherited restrictions, protected files, excluded roots, invalid IDs, malformed cursors, oversized cursors, missing roots and noncanonical URLs. Expected result is omission/404 for ineligible records, 400 for invalid cursors, and 503 for incomplete source reads. Never emit
complete=trueafter a failed traversal. - Verify all imports/method signatures against the installed 2026.08 runtime. Current packages use
@sitevision/api2026.9.1, so successful compilation alone is insufficient. Pin compatible definitions or prove each used API is available on the installed build. - Test the manifest under anonymous online execution on staging. The website restricts Model REST access to groups; verify whether the connector's internal
online.get(..., 'nodes')is affected. If it is denied, use a supported traversal preserving anonymous checks, or involve the administrator for a narrow supported solution. Do not open the entire Model REST API to anonymous users. - Confirm complete local-file repository discovery and exclusion markers. Add an explicit repository traversal or approved root where necessary. A root page's children cannot be assumed to enumerate its local files.
- Attempt prompt event delivery after durable storage for all relevant event types. Keep retries, but replace the fixed 25-events-per-five-minutes bottleneck with bounded draining, backoff and observable backlog. Test a 300-event burst, receiver outage, duplicate delivery, cluster overlap and restart. Do not remove outbox records before a successful acknowledgement.
- Review datastore return shapes and event payloads on the tenant. Verify
options.node, event identity, stored-record identity and ordering behavior instead of trusting mocked payloads. - Scope emitted events to the integration's needs where possible; keep enough ancestor information to handle moves out of scope. Do not export unrelated private node details.
- Build a new immutable package version and record checksum, source revision and compatibility result. Obtain signing through the approved developer team. Trust installation is a specific security-sensitive approval, separate from package preparation.
Exit: Connector unit tests pass, packages are signed, and the staging manifest/document endpoints return the expected bounded public representation. No production installation yet.
Task 4: Make synchronization sustainable under short leases
Owner: BCW. Dependencies: Tasks 2–3 for tenant validation; mock implementation can start earlier. Outside intervention: Modum agreement on freshness and permission-change fixtures.
Files: lib/search/refresh.ts, lib/search/sync.ts, lib/search/store.ts, lib/search/types.ts, scripts/sync-worker.ts, tests/refresh.test.ts, tests/sync.test.ts, tests/store.test.ts, connector route implementation.
Current gap: Every two minutes the worker inventories and queues all sources. File refresh downloads/extracts before hash reuse; eligibility renewal competes with embedding work. Under load this can exceed five-minute leases. Every upsert also bumps the revision used by cursors and cached documents.
Proposed additional connector contract:
type EligibilityItem = {
sourceId: string;
eligible: boolean;
eligibleUntil: string;
ancestorIds: string[];
url?: string;
sourceRevision?: string;
};
// GET /eligibility?ids=<URL-encoded JSON array of at most 50 source IDs>
// Same HTTPS/key/Anonymous/online checks as other export routes.
// Response: { items: EligibilityItem[] } with exactly one item per requested ID.
// Invalid or incomplete reads fail the batch; absence must not imply eligibility.
- Define and validate this contract on both sides. The route renews public eligibility without rendering the full body, downloading binaries or calling the embedding provider. A failed batch renews nothing.
- Separate high-priority eligibility checks and revocation handling from content refresh jobs. Start renewal near half of the five-minute lease, with a bounded concurrency and per-tenant request budget. Keep full inventory and content hash scans as independent jobs with visible completion metrics.
- Renew eligibility only after current publication, origin, scope, ancestor and anonymous access checks. Apply generation/safety guards to renewal transactions too. A changed content revision must schedule a body refresh; if revision detection is unreliable, periodic content scans remain mandatory.
- Cache downloaded/extracted file content using a validated source revision or HTTP validators. Revalidate public eligibility regardless of whether content bytes changed. If no reliable revision exists, refresh the body on the documented schedule rather than assuming it is unchanged.
- Make lease-only updates refresh access state without unnecessarily invalidating every result cursor. Keep a separate content/ranking revision and eligibility generation, or explicitly restart pagination with a useful UI message. Any page of results still checks current eligibility.
- Test an unchanged full corpus with slow OCR jobs in progress: every eligible document renews before expiry without re-embedding unchanged content. Then disconnect the connector and verify all expired records disappear from results and suggestions.
- Test parent restriction during child fetch/embedding/renewal; old publish after unpublish; moves across roots; trash/restore; failed and truncated manifests; duplicate events; dead-event recovery. Retain all existing suppression and generation tests.
- Build the CMS corpus in a separate database from
crawl:*records. At cutover, use only the approved CMS index; do not leave duplicate public-crawl copies capable of surviving a CMS revocation.
Exit: Full approved-corpus renewal is sustainable with measured headroom, mutations converge correctly and incomplete scans cannot delete the corpus. If the lease target cannot be met within SiteVision's supported load, stop for a design decision on bounded current-access checks or a revised agreed policy. Do not silently lengthen leases.
Task 5: Complete API and retrieval production behavior
Owner: BCW. Dependencies: Tasks 1–2; completes after full-corpus testing. Outside intervention: none for code; traffic expectations and production provider ownership come from Gates C/D.
Files: lib/search/api.ts, lib/search/ranking.ts, lib/search/embeddings.ts, lib/search/query-cache.ts, tests/api.test.ts, tests/search.test.ts, tests/query-cache.test.ts.
- Add bounded query-embedding caching keyed by normalized query and exact model/dimensions/version. Do not cache final result eligibility. Clear or segregate cache when models change.
- Replace the current process-wide 300-requests-per-minute counter with deployment-level abuse controls and a bounded application concurrency budget. Account for a SiteVision proxy sharing an IP, and trust forwarded client identity only from the configured proxy. Test that one noisy client does not exhaust all ordinary search traffic.
- Test provider 401/429/timeout/5xx, empty index, dimension mismatch, invalid filters, duplicate parameters and oversized input. Preserve distinct no-results, degraded keyword mode and service-failure behavior.
- Test a next-page request across routine lease renewal, content updates and a model outage. Do not silently change the ordering mid-pagination; restart with an explicit message if the cursor is no longer valid.
- Add relevance-based weak-match handling using judged examples in Task 7. Preserve exact service names, file titles and identifiers; do not choose a universal similarity cutoff from intuition.
- Benchmark in-process retrieval on the complete approved corpus under agreed concurrency. Retain current storage if it passes; if it fails, design a separate indexed lexical/vector backend migration with the same public contract and a rebuilt shadow index. Do not add a distributed database as speculative work.
Exit: API validation, degradation, pagination, tenant boundaries and load behavior pass the agreed test suite. Model costs and query budgets are measurable.
Task 6: Finish the actual SiteVision search experience
Owner: BCW. Dependencies: API contract; CMS staging for integration. Outside intervention: Modum approves the visual result and any shared-header/template edits.
Files: integrations/sitevision-search/src/index.js, src/view.js, src/config/index.html, optional src/main.js and scoped assets, test/view.test.js, test/requests.test.js, plus the selected SiteVision page/template configuration.
- Bring the CMS module to feature parity with the local prototype: type/language filters, useful source metadata, suggestions, loading/error/empty states, query persistence and pagination. Do not assume the local
/sokUI is what gets installed in SiteVision. - Parameterize result-page path, accepted source origin and a separate original-search fallback path. Current hardcoded
/sokandwww.modum.kommune.noprevent a clean staging/test-page deployment. - Keep the first result response server rendered with ordinary GET forms. Add same-origin progressive suggestions without a browser API key; a failed enhancement must not break form submission.
- Define a CMS-to-API timeout consistent with the approved page-render budget. Test non-JSON responses, malformed results, invalid links, API 429/503, expired cursors and empty results. Escape titles, snippets and queries in every rendering path.
- Preserve homepage and header behavior, remove duplicate legacy autocomplete hooks and test bookmarked
/sok?query=...URLs. Keep the original module available on a dedicated fallback page. - Test keyboard-only search, suggestion navigation, focus announcements, labels, contrast, 200% zoom, mobile widths and browser back/forward. Verify pagination/filter links retain the query and do not produce a fallback loop.
- Check staging native search alongside the independent API if the entitlement is available. Record quality, coverage, latency and operating-cost differences; retain the independent design unless the product owner chooses otherwise.
- Sign the immutable WebApp package after its tests and staging theme checks pass.
Exit: The installed staging module, not merely the standalone prototype, passes functional and accessibility acceptance with a working original-search fallback.
Task 7: Reconcile coverage and obtain relevance acceptance
Owner: BCW engineering and Modum editorial representatives. Dependencies: Tasks 2–6. Outside intervention: Gate G is mandatory before launch.
Files: evaluation/resident-tasks.json, scripts/evaluate-search.ts, lib/search/ingestion.ts, lib/search/files.ts, lib/search/ranking.ts, tests/ingestion.test.ts, tests/search.test.ts, docs/acceptance.md.
- Classify every approved manifest record as indexed, intentionally excluded, failed or unsupported. Reconcile unique source IDs and canonical URLs rather than expecting equality with the old crawl's 1,118 records.
- Review the crawler's last recorded 111 failures/exclusions against the authoritative inventory. Determine whether the 12 MB download limit, 200-chunk limit, empty extraction and unsupported formats omit approved content. Raise limits only with bounded-memory tests and explicit scope approval.
- Add heading/table-aware chunking and meaningful PDF page references where extraction supports them. The current fixed-window chunker and text-only file extraction do not guarantee these. Test a table crossing a chunk boundary, a scanned PDF and a replaced document version.
- Replace provisional URL-pattern judgments with 50–100 tasks carrying explicit relevant source IDs and relevance grades. Include Bokmål, Nynorsk, agreed English scope, typos, exact titles, service identifiers, historic documents and unrelated queries.
- Include housing support, kindergarten applications, building permits, legevakt and rubbish collection. For external-service questions, label the correct service link or no-result outcome rather than treating an arbitrary municipal document as success.
- Run existing search, lexical-only and hybrid on the same corpus/task judgments. Include native semantic search if accessible. Report useful result at 3/5, ranking quality, critical-query regressions, duplicates, no-result quality and latency.
- Tune on a development subset; reserve a held-out subset for acceptance. Proposed initial acceptance is at least 90% useful top-five results on judged in-scope tasks, improvement over lexical search, and no material regression on the agreed critical service tasks. Modum must approve or replace these targets before evaluation is treated as acceptance.
- Obtain an editorial decision on every remaining coverage exception and an explicit pass/fail record for relevance and accessibility.
Exit: An approved corpus with accountable exceptions and a judged relevance report. More hits alone is not evidence of better search.
Task 8: Deploy and operate the external service
Owner: BCW engineering/operator. Dependencies: Gate C and provider decision. Outside intervention: Hosting/DNS/account access, spending approval where needed, alert recipient and support owner.
Files: Dockerfile, compose.yaml, .dockerignore, .env.example, app/v1/health/route.ts, app/v1/admin/sync/status/route.ts, scripts/backup-search.ts, scripts/restore-search.ts, scripts/load-search.ts, tests/operations.test.ts, docs/operations.md.
- Start with one persistent host for the API and CMS worker, unless measured load requires otherwise. SQLite must use local durable storage shared by processes on that host; do not run independent writable replicas or depend on an ephemeral deployment filesystem.
- Build and run the container on the selected staging host. The local Docker daemon was previously unavailable, so container behavior is unverified. Run as a non-root user, exclude secrets/data from image layers and validate native file-extraction tools.
- Provision HTTPS, DNS, persistent volume, supervised restart and approved secrets. Keep admin routes authenticated and network-restricted; expose only needed public routes. Configure connector receiver origin exactly.
- Set distinct
SITEVISION_CONNECTOR_KEY,SEARCH_SYNC_TOKEN,SEARCH_ADMIN_TOKENandSEARCH_CURSOR_SECRET, plus approved model credentials. Store them in the deployment secret system. Do not send values in chat, logs, browser state or release notes. - Separate process liveness from index readiness. The current health endpoint succeeds when just one eligible document remains; add coverage, worker freshness, lease-renewal lag and backlog checks so partial index collapse is visible.
- Monitor API p50/p95/errors, keyword fallback rate, provider spend, live/expired document counts, extraction failures, oldest event age, dead events, outbox backlog and last completed inventory. Alert on actionable thresholds to the named operator; omit query text by default.
- Implement SQLite-consistent backups using its supported backup/snapshot mechanism. Do not copy only the live database file and ignore WAL state. Encrypt backups, apply agreed retention and perform an isolated restore drill.
- After restoring, keep search unready until current eligibility has been revalidated. Replaying old backup content must not resurrect revoked records. Proposed recovery target for agreement is four hours, with a daily backup and source reconciliation covering changes since backup.
- Run sustained load, burst load, CMS-worker contention, provider outage, disk-full and process-restart tests. Test both direct API and SiteVision-rendered latency against the approved traffic model.
Exit: Managed staging service with HTTPS, persistent storage, supervised workers, working alerts and a successful recovery drill. Record actual monthly hosting estimate and measured model usage before production commitment.
Task 9: Validate CMS changes end to end
Owner: BCW with a Modum/SiteVision administrator for permission changes the account cannot perform. Dependencies: Tasks 3–8 staging deployment. Outside intervention: Gates B/E, and designated test content.
Files: docs/acceptance.md, regression tests in tests/sync.test.ts, tests/refresh.test.ts and connector tests for any defect found.
- Publish and republish fixtures; edit an unpublished draft; schedule unpublish; move across approved roots; trash and restore; replace and select file versions. Confirm the final indexed state and canonical URL each time.
- Ask the administrator to restrict an individual fixture and an ancestor because our account lacks ordinary permission management. Verify both search and suggestions cease returning descendants, including cached-query and second-page requests.
- Delay an old publish event until after removal and simulate duplicates, missed events, receiver outage, connector restart and authentication failure. Measure detection and suppression rather than checking only eventual recovery.
- Interrupt a manifest midway, and move siblings while scanning. Verify the previously valid corpus is not mass-deleted and the next completed reconciliation converges.
- Disable the embedding provider in staging, then the search service. Verify lexical fallback for the former and the original SiteVision search path for the latter. Existing API result caches must respect current eligibility.
- Fix each failing scenario with a regression test and rerun the affected acceptance slice. Re-run the release suite after code changes.
Exit: Signed packages and hosted service pass the documented mutation/failure matrix on real SiteVision, including tests needing administrator intervention.
Task 10: Release, rollback and operational handover
Owner: BCW release operator and Modum website owner. Dependencies: All prior exit conditions. Outside intervention: Gates F, G and H.
Files: docs/release-runbook.md, docs/operations.md, docs/acceptance.md, README.md, docs/implementation-progress.md; versioned packages and deployment configuration.
- Freeze tested source revisions and package checksums. Record old
/sokmodule/template settings, old autocomplete behavior, a separate working original-search page and the previous service/index deployment. - Review the exact proposed trust and connector access settings with the authorized owner. Apply security-sensitive access changes only after their specific approval. Never grant ordinary visitors Model REST access or privileged write actions for search.
- Install the signed production connector as a new version, configure only approved roots/origins and enable the dedicated event receiver. Verify eligible exports from the worker without editor cookies.
- Build and reconcile a separate production CMS index while the existing public search remains active. Verify inventory counts, renewals, critical queries and provider costs. Keep prototype crawl records out of this production index.
- Install the signed search module on an approved preview route first. Verify that configuration points at the production service and original-search fallback, not staging or localhost.
- Obtain the explicit launch decision after the acceptance packet is complete. Switch the
/sokresults module and agreed homepage/header suggestion hooks in a coordinated release window. - Run public smoke checks for the agreed critical queries, bookmarked URLs, mobile, pagination, PDF results and fallback. Observe errors, coverage and latency immediately after the switch.
- Roll back if a private/draft result appears, critical service navigation fails, approved coverage drops unexpectedly, or error/latency thresholds remain outside target. Restore the original
/sokmodule and autocomplete first; investigate independently. Do not restore an old index into public service without renewed eligibility checks. - Observe for a proposed seven-day window, then close remaining defects and hand over alerts, runbooks, backup/restore steps, key-rotation ownership and support contacts. Retain a tested fallback for the agreed support period.
Exit: Accepted production service, named operating owner, documented support expectations and proven rollback. No other Modum content or external systems are modified as a side effect.
Evidence and review required for each engineering task
For a behavior change: add the named failing case, run it to demonstrate the failure, implement the smallest change, run the focused suite, review the diff and make a scoped commit. Do not add ceremonial tests for documentation or unchanged behavior. The named tests above describe required assertions; the executor must implement them against the actual interfaces rather than copying mock SiteVision internals into production code.
After cross-cutting changes, run:
pnpm test
pnpm exec tsc --noEmit
pnpm lint
pnpm build
npm --prefix integrations/sitevision test
npm --prefix integrations/sitevision run build
npm --prefix integrations/sitevision-search test
npm --prefix integrations/sitevision-search run build
The connector test command is added in Task 3. Evaluation and load tests require approved datasets, provider quotas and the correct environment; do not run mutation or load tests against production just to complete a checklist. Keep evidence of tenant version, package hashes, test outcomes, corpus accounting and approval decisions together in docs/acceptance.md.
Effort and scheduling
These are planning estimates after reviewing the prototype, not a fixed quote or a promise of calendar delivery. They assume one engineer, a staging tenant supplied promptly and no custom module that requires substantial new extraction work.
| Work | Estimated engineering effort | Main uncertainty |
|---|---|---|
| Baseline, source contract and access preparation | 1–2 working days | Content-owner availability and canonical URL/repository details |
| Connector and sustainable synchronization | 3–5 working days | Tenant runtime behavior, event delivery and lease renewal load |
| API hardening and CMS UI completion | 2–4 working days | Pagination under updates, template integration and accessibility |
| Hosting, monitoring, backups and load validation | 2–3 working days | Infrastructure access and measured capacity |
| Corpus/relevance acceptance, release and handover | 2–4 working days | Editorial judgments and tenant defects |
Total initial budget: 10–18 engineer-days, plus external waiting time and a proposed seven-day observation period. Signing/team access, staging provisioning and Modum acceptance have no verified turnaround time. Re-estimate after the 30–50-item source-contract test. A new search database, unsupported custom modules, another model provider, strict availability requirements or external-system connectors are explicit scope changes.
Immediate next implementation actions
Once execution is requested, complete Task 1 and prepare one consolidated request for signing/team access, CMS staging, hosting ownership and Modum's content/acceptance owner. Continue Tasks 2–6 locally while those inputs arrive. Do not ask for blanket administrator access or unrelated API keys.
The first unavoidable external dependency is signed-app deployment to a suitable test environment. The first unavoidable editorial dependency is approving source scope and the expected answers for resident tasks. The final external decision is approval to switch the public search after the evidence is ready.
References
- Existing specification and setup guide linked above, including their extraction/event/reconciliation references.
- SiteVision certificate and signing workflow: developer-team membership, signing, website trust and production deployment.
- RESTApp deployment: versioned add-ons and activation.
- RESTApp restrictions: request access, HTTP methods and service-user configuration.
- Permission panels and role management: separate rights from ordinary website settings.
Plan self-review
The original specification's public scope, source extraction, files, anonymous eligibility, events, reconciliation, hybrid retrieval, /sok integration, native comparison, signing, operations, evaluation and rollback are covered by Tasks 1–10. External source records are explicitly separate scope. The baseline numbers remain historical observations. Proposed service and acceptance targets are marked for agreement. No production write, new key, certificate trust or deployment has been performed to prepare this plan.