| REQ-DOC-001 |
Every document shall belong to exactly one scope and have exactly one document type from the types activated in that scope. |
STR-001, STR-003; ADR-0002, ADR-0004 |
| REQ-DOC-002 |
Every document shall carry an immutable, human-readable document ID of the form [<SCOPE-CODE>-]<TYPE-PREFIX>-<DESIGNATOR>; locally created documents carry their scope's code, the global scope is unprefixed. IDs are unique per scope and type; a derived document retains its source ID (REQ-DOC-011), so the same ID may appear in the source scope and in scopes that derived it — uniqueness is therefore namespaced by (scope, ID). |
STR-001; §8.1 |
| REQ-DOC-003 |
The designator shall be an auto-sequence number (per scope and type) or a validated slug ([A-Z0-9_], length-limited, reserved words excluded), according to the numbering mode configured on the document type. |
STR-001; §8.1 |
| REQ-DOC-004 |
A document shall consist of an ordered series of versions; every version shall record its author, life-cycle state, and content reference. |
STR-001, STR-009; ADR-0003 |
| REQ-DOC-005 |
Version content shall be either authored in-tool (structured content) or uploaded file(s); both shall behave identically in life cycle, search, and audit. (refined for multi-part content; in-tool authoring realized as Markdown text with embedded image attachment parts) Delta note (2026-07-10, ADR-0057/0058): in-tool authoring is now a ProseMirror tree record (content_format=PM_DOC) with a derived canonical Markdown projection; the tree is authoritative, the projection is the readable/searchable body part. PM_DOC is the only format written (ADR-0058). |
STR-001, STR-007; ADR-0045, ADR-0057/0058 |
| REQ-DOC-014 |
A version's content shall be an ordered set of one or more named content parts, each stored as an immutable blob (ADR-0001); single-content documents have exactly one part. (part kinds extended by REQ-DOC-026/029: attachment DISPLAY/FILE parts and diagram scene/SVG pairs; ADR-0069, ADR-0065) |
STR-001; ADR-0017, ADR-0045, ADR-0069, ADR-0065 |
| REQ-DOC-015 |
The version's integrity/signing hash shall be a manifest hash computed over the ordered set of parts (part name, part hash, content type, size); the canonical manifest serialization shall be persisted as a blob so the signed hash is verifiable independently of the database representation. Byte-exact reproduction (REQ-DOC-007) and signature binding (REQ-SIG-005) operate on the manifest. |
STR-009; ADR-0017, ADR-0014, ADR-0025 |
| REQ-DOC-016 |
Concurrent edits to a draft shall use optimistic locking: a save based on a stale version token shall be rejected with a clear message, never silently overwriting a concurrent change. |
STR-001, STR-007; ADR-0003 |
| REQ-DOC-017 |
Creating a new version of an existing document shall require a change description (reason/summary of what changed); it is recorded with the version and reproducible in the audit/inspection view (REQ-AUD-003/006). (extended by REQ-DOC-031: the reason is DRAFT-editable, frozen at submit, and joined by an optional impact note — ISO 13485 §4.2.4.) |
STR-009; ADR-0001 |
| REQ-DOC-018 |
The storage interface shall support deletion of a blob by hash; disposal (REQ-DPR-005) shall delete a version's content-part and manifest blobs via the interface (distinct from orphan garbage collection), and derived copies in other scopes are disposed independently. |
STR-021; ADR-0025, ADR-0001 |
| REQ-DOC-006 |
On every content-freezing life-cycle transition, version content shall be serialized to a canonical form, hashed (SHA-256), and stored as an immutable blob; the version shall reference the blob by hash. Delta note (2026-07-10, ADR-0057/0058): a PM_DOC version freezes BOTH the authoritative tree part (application/prosemirror+json, kind=TREE) and its derived Markdown projection body part; the manifest hash binds both so byte-exact reproduction (REQ-DOC-007) covers the whole record. |
STR-009; ADR-0001, ADR-0057 |
| REQ-DOC-007 |
The content of a released version shall be reproducible byte-exactly via its hash, and the system shall provide an integrity verification (re-hash and compare) for any stored blob. |
STR-009; ADR-0001 |
| REQ-DOC-008 |
Blob storage shall be accessed exclusively through a storage interface with a file-system implementation; an S3-compatible implementation shall be addable without changes outside the storage module. |
STR-013; ADR-0001, ADR-0005 |
| REQ-DOC-009 |
The blob directory layout shall be partitioned by scope; content shall never be deduplicated across scopes. |
STR-003; ADR-0001 |
| REQ-DOC-010 |
The derive operation shall copy the full content of a released source version into the target scope as a new document, recording a version-level provenance link (source document and exact version). |
STR-004; ADR-0002 |
| REQ-DOC-011 |
Derived documents shall inherit the source's full document ID unchanged. |
STR-004; §8.1 |
| REQ-DOC-012 |
When a new version of a derivation source is released, the system shall notify the affected scopes; derived documents shall never change implicitly. |
STR-005; ADR-0002 |
| REQ-DOC-013 |
A garbage-collection job shall remove blobs no longer referenced by any version; referenced blobs shall never be deleted. (the two-phase quarantine bin — quarantine then purge — is added by REQ-DOC-038; realized ADR-0092.) |
STR-009; ADR-0001, ADR-0092 |
| REQ-DOC-019 |
The system shall provide, per scope, a folder tree for organizing documents: folder rows are scope-keyed content-plane data subject to RLS (invisible across scopes), sibling names are unique within a parent, tree depth is bounded (max 8 levels), a folder's parent must be in the same scope, and move/re-parent operations shall re-validate the ancestor chain so no cycle can form. The root is implicit (no row). |
STR-007, STR-003; ADR-0048, ADR-0027 |
| REQ-DOC-020 |
Each document shall have an optional folder placement within its scope (nullable; unset = scope root); all of a document's versions share the placement, new versions/revisions inherit it, and a derived copy lands at the target scope's root. Moving a document is organizational metadata only: it shall never create a version, re-freeze content, or otherwise alter any version's content, identity (document ID), or signatures (REQ-DOC-017, REQ-DOC-015). |
STR-001, STR-009; ADR-0048 |
| REQ-DOC-021 |
Folder create, rename, reorder, and re-parent operations and document moves shall be gated by a dedicated ORGANIZE_DOCUMENTS permission (VIEW suffices to read the tree) and shall each produce an audit event (FOLDER_CREATED/FOLDER_RENAMED/FOLDER_DELETED, DOCUMENT_MOVED; scope-bound, actor-attributed). A folder shall be deletable only when empty (no documents and no child folders); no tree operation shall cascade onto documents or records. |
STR-003, STR-009; ADR-0048, ADR-0004, REQ-AUD-001 |
| REQ-DOC-022 |
Each document type shall carry exactly one category from the fixed set PRESCRIPTIVE, RECORD, TEMPLATE, acting as a capability flag: periodic review and validity (REQ-EFF) apply to PRESCRIPTIVE-category documents only; retention and disposal semantics stay anchored on RECORD closure; TEMPLATE-category documents offer the instantiate affordance (REQ-DOC-024). The category set is exactly these three. |
STR-001, STR-020, STR-021; ADR-0049, ADR-0015, ADR-0040, ADR-0004 |
| REQ-DOC-023 |
A TEMPLATE-category document body may declare template variables in a fenced template-vars block (one key \| label \| required \| default per line) and reference them as {{key}} placeholders; the block shall render as a definition table in the preview, not as content. At SUBMIT of a template version, every {{key}} used in the body must be declared, otherwise the content-freeze shall be blocked with a human-readable error naming the undeclared keys. |
STR-001, STR-004, STR-007; ADR-0049 |
| REQ-DOC-024 |
Instantiating a template shall be permitted only from a RELEASED, currently-effective TEMPLATE version and shall require CREATE_DOCUMENT in the target scope and a target type that is RECORD-category and activated in that scope; required variables must be supplied (otherwise a human-readable error naming the missing keys). The result shall be a new RECORD-category draft whose body is the template body with placeholders substituted and the template-vars block stripped, carrying version-precise provenance to the source template version (an instantiated-from relationship, REQ-REL-002) plus an INSTANTIATED audit event. Variable values are not re-bindable after creation. |
STR-004, STR-001, STR-009; ADR-0049 |
| REQ-DOC-025 |
The controlled markdown record format shall support in-text document links of the form [text](doc:<DOC-ID>), with an optional scope-qualified form doc:<SCOPE-CODE>/<DOC-ID>, targeting the immutable human document ID (REQ-DOC-002) so that folder moves and title changes never break a link. Bare-ID resolution shall prefer a same-scope holder, then other holders visible to the caller. The link text is authored content and shall freeze verbatim with the version. At view time a link shall resolve through the caller's own RLS-checked API to the latest visible version; a target that is unresolvable or not visible to the caller shall render as inert styled text — no error and no disclosure of the target's existence. Delta note (2026-07-10, ADR-0057/0058): the record is now the tree (PM_DOC); an in-text document link is a tree node whose canonical projection is [text](doc:<DOC-ID>) — the doc: contract, freeze-verbatim link text, and view-time RLS resolution are unchanged. |
STR-001, STR-009, STR-008; ADR-0051, ADR-0057 |
| REQ-DOC-026 |
A version's attachments shall fall into two classes: DISPLAY (the sniffed image set — PNG/JPEG and the Excalidraw SVG/scene-JSON pair — embeddable in content via <img>) and FILE (any other type, stored byte-exact with a sniffed-or-declared media type falling back to application/octet-stream). There shall be no type blocklist for FILE attachments — a QMS cannot predict record formats, and download-only confinement (REQ-DOC-027), not scanning, is the control. |
STR-001, STR-009; ADR-0069, ADR-0045 |
| REQ-DOC-027 |
FILE-class attachments shall be download-only: the serving endpoint shall force Content-Disposition: attachment together with X-Content-Type-Options: nosniff and a per-response Content-Security-Policy: default-src 'none'; sandbox, so a FILE attachment never renders in a browsing context (neutralizing the HTML/PDF/active-content XSS family). Malware screening is out of scope — an authenticated internal record store behind confinement; endpoint protection is the organization's control. |
STR-009, STR-014; ADR-0069 |
| REQ-DOC-028 |
Attachments shall share the version content lifecycle: DRAFT-only upload and delete (EDIT_DRAFT-gated, size- and count-limited, audited); on a content-freezing transition they materialize into manifest content_part rows carrying their kind (DISPLAY/FILE) and are covered by the manifest hash (REQ-DOC-015); reject/withdraw restores them to the draft working area with names and hashes preserved; a new version pre-seeds the predecessor's frozen attachments; disposal severs them (REQ-DOC-018, REQ-DPR-005); derive/copy carries them into the target scope; and an attachment shall never be readable across scopes (RLS → 404). |
STR-001, STR-003, STR-009; ADR-0069, ADR-0065, ADR-0045 |
| REQ-DOC-029 |
A diagram shall be a content-tree node referencing a content-part pair — the editable Excalidraw scene.json source plus a rendered .svg for display — both stored through the attachment machinery (REQ-DOC-014) and frozen with the version; Mermaid remains available for text-first / UML / AI-generated diagrams. The canonical Markdown projection of a diagram node degrades to an image reference (the tree and content parts remain the lossless record, REQ-DOC-005). Delta note (2026-07-15, ADR-0072): the diagram engine is now self-hosted draw.io and the artifact is a SINGLE xmlsvg content part (the display SVG with the editable source XML embedded), replacing the scene+SVG pair; double-click re-opens it for editing. Imported draw.io diagrams enter as EDITABLE diagram nodes (their source XML as the artifact) rather than static previews; a node may carry a transient preview image that the first save upgrades away. The display path (attachment:→<img>) is unchanged. |
STR-001, STR-007; ADR-0065, ADR-0072, ADR-0057, ADR-0045 |
| REQ-DOC-030 |
A diagram's SVG shall be displayed as an <img> through the attachment: resolver (structurally scoping the SVG's @font-face/styling away from the app document and adding no sanitizer trust surface), and diagram export shall emit zero external network requests — the exported SVG shall be self-contained (fonts embedded; the font-harvester's external-CDN fallback suppressed) — with this "zero external requests on export" property pinned in the full-stack smoke. _Delta note (2026-07-15, ADR-0072): the editor is now a self-hosted draw.io embed served from a dedicated static path under its own iframe CSP (script-src 'self', no unsafe-eval; the main app adds frame-src 'self'); the runtime assets are fetched at build from a SHA-pinned release, never committed. The zero-external-requests property is re-pinned against the vendored draw.io in the full-stack smoke and the diagram e2e. |
STR-014, STR-007; ADR-0065, ADR-0072 |
| REQ-DOC-031 |
Creating a new version shall carry a reason for change (REQ-DOC-017: why the change is made, required) plus an optional impact note (what is affected — other documents, training, downstream records), stored as two distinct fields. Both shall be editable while the version is DRAFT (permission-gated by EDIT_DRAFT, audited CHANGE_INFO_SET) and FROZEN at SUBMIT alongside the content — pinned in the SUBMITTED audit so the round's approvals attest them — and surfaced on the version detail and history; a first version (v1) carries neither. |
STR-009; ADR-0001 (ISO 13485 §4.2.4) |
| REQ-DOC-032 |
An imported document's version numbering shall continue the source system's sequence (a create may start at a provenance-supplied version number; later revisions continue from it), and a structured source_history claims record — Confluence page versions and the Jira ticket changelog (actors + timestamps, names as text linked to LQMS accounts only where they exist) — shall be stored on the document (RLS scope-carrying, severed on disposal) and displayed as a clearly-marked "History from the source system" section, distinct from the LQMS audit trail. The system shall never fabricate LQMS lifecycle acts for historical versions (the red line): the source history is provenance-with-attribution, not LQMS audit, and historical content bodies are not recreated as LQMS versions. |
STR-026, STR-009; ADR-0075, ADR-0068 |
| REQ-DOC-033 |
A post-working version — RELEASED (any effective standing) or REVOKED — shall export as a server-rendered PDF via the canonical Markdown projection (REQ-DOC-005), authenticated and RLS-scoped; DRAFT, IN_REVIEW, CANCELLED and disposed versions shall refuse with 409 (a working copy has no controlled identity to stamp). Every export shall be audited (EXPORTED, actor + version) and shall bear a first-page identity header (document ID, title, version, state + effective standing, effective date, scope, exporter) and, on EVERY page, the distribution-control band "UNCONTROLLED COPY — exported \<date> by \<user>" plus page N / total (ISO 13485 §4.2.4). Content with no server-renderable form shall degrade visibly and documented (Mermaid → labelled source block, math → literal $latex$, doc:/attachment: links → text), never silently dropped; draw.io diagrams and images render inline. (navigation — heading bookmarks/anchors and context-dependent doc: link resolution — added by REQ-DOC-036; multi-document scope export by REQ-DOC-037; ADR-0074 addendum, ADR-0083.) |
STR-009, STR-011; ADR-0074, ADR-0057, ADR-0062, ADR-0083 |
| REQ-DOC-034 |
A requirement shall be a keyed block INSIDE a controlled document (tree node requirement, fence :::requirement), never a separate entity: it carries a scope-unique key plus the FIXED optional attribute set (kind — functional | non-functional | stakeholder | safety | other; priority; rationale; acceptance criteria) with no custom-attribute engine. Its identity and versioning shall be DERIVED, never managed — the key is stable and the block's content hash across document versions IS its history; the DOCUMENT's lifecycle is the item's lifecycle (no separate item state). A key shall be unique per scope and DEFINED in exactly one document (moving a definition is a deliberate delete-and-redefine, both under document review); links to a requirement may originate anywhere. Whether a document type may define requirements shall be a per-(scope, type) may-define governance flag (scope config); keys follow scope-configured auto-numbering conventions with save-time uniqueness against the scope registry. The block shall round-trip losslessly through the Markdown projection and be swept through the full content lifecycle (freeze, derive, disposal, RLS, search, PDF). |
STR-001, STR-009, STR-027; ADR-0077, ADR-0057, ADR-0073 |
| REQ-DOC-035 |
A requirement item shall be one concrete TYPE of a generalized trace item — a keyed item inside a controlled document discriminated by TYPE, each type declaring a FIXED per-type attribute schema (no custom-attribute engine); the requirement is type = requirement. Keys shall be unique per scope across ALL trace-item types (the prefix conventions keep them apart). Registering a later type (its tree node, fence, and attribute schema) shall be ADDITIVE — the extraction, registry, link, suspect and coverage engines are parameterized by type and shall not change. Backward compatibility is non-negotiable: existing FROZEN content and the :::requirement serialization stay byte-exact (the fence is never renamed); any trace-neutral internal renaming (e.g. requirement_link → trace_link) shall be a data-preserving migration that alters no endpoint semantics or frozen content. |
STR-001, STR-009, STR-027; ADR-0078, ADR-0077, ADR-0057 |
| REQ-DOC-036 |
The PDF renderer (REQ-DOC-033) shall emit navigation as CONTEXTS of the one render path, not a second surface: every heading shall carry a stable GitHub-style slug id and a fo:bookmark-tree outline nested by heading level; an in-text #fragment link shall resolve to the matching heading and an unmatched fragment shall degrade to inert text (never a dead link). doc: link resolution shall be context-dependent — a single-document export renders inert text + (HUMAN-ID); in an audit-pack zip a target present in the pack becomes a relative cross-file link to <humanId>_v<n>.pdf (a target absent from the pack degrades to text + id); in a merged export the same target becomes an internal link to that document's chapter. A merged pack PDF shall carry a cover, a clickable table of contents, and each document as a chapter with its own bookmark subtree, its §4.2.4 identity header, and continuous page numbering. |
STR-009, STR-011; ADR-0074, ADR-0083, ADR-0051 |
| REQ-DOC-037 |
The system shall export a per-scope AUDIT PACK (GET /api/scopes/{scopeId}/audit-pack) that streams a zip containing the currently-effective RELEASED version of every document the caller can see in the scope, each rendered through the single-document PDF path (REQ-DOC-033: §4.2.4 identity header + uncontrolled-copy footer band), plus manifest.json and manifest.txt recording the pack facts and a sha256 of every PDF (the evidence-grade layer a verifier re-hashes). By default (includeReferenced, opt-out) the pack shall augment the scope's own set with the TRANSITIVE CITATION CLOSURE — every document reached by following outgoing citations (document relations incl. from_text, and trace links resolved to documents), each at its effective-released version, in any scope the caller can see — with RLS bounding the closure absolutely (never another scope's data). Authorization shall mirror the single-document gate at scope level (VIEW in the scope; a foreign caller 403s before any 200 opens) and one scope-level EXPORTED event shall record the pull only AFTER the whole pack is written; the zip streams incrementally with manifest.txt as the LAST entry — the completeness marker — so a render failure mid-stream produces no audit event and a truncated, detectably-incomplete zip. ?format=merged shall return instead one merged reviewer's-copy PDF (REQ-DOC-036); the zip and the merged PDF are separate artifacts, never double-bundled. Both shall be served download-only with the attachment-hardening headers (REQ-DOC-027). |
STR-009, STR-011, STR-003, STR-027; ADR-0083, ADR-0074, ADR-0060, ADR-0069 |
| REQ-DOC-038 |
The garbage-collection job (REQ-DOC-013) shall delete an orphan blob in TWO phases — quarantine, then purge. A periodic sweep shall record each unreferenced blob in a scope-keyed blob_quarantine registry (a registry mark, not a file move — the bytes stay in place so content-addressed dedup, get, and rescue keep working); a later pass shall permanently delete a quarantined blob only after it has sat the configured quarantine period (default 30 days) AND is re-verified unreferenced at purge time. Reference truth is the database: a blob is live when referenced by any non-disposed row of a blob-referencing column, the set of such columns enumerated in one place, with an architecture test asserting the schema's hash/blob-named columns equal that enumeration plus documented non-reference exclusions (a new blob-referencing column fails the build until the GC inventory is extended). A quarantined hash referenced again shall be rescued (removed from the bin); a failed physical deletion shall leave an orphan the next sweep re-quarantines (self-healing in both directions); a purge that deletes blobs shall write an audit event with per-scope counts. |
STR-009; ADR-0092, ADR-0025, ADR-0042 |