DisclosureOS documentation · v2 Beta
Witnesses and accounts
Connect each recorded account to its speakers, sources and recording context, then review it separately.
A witness is a person. An account is one recorded telling of what that person reported. Keep them separate so a later interview can refer to the same witness without being counted as another witness. A review records what someone concludes about a particular account or declaration.
This guide follows one fictional witness through two accounts. You will run the published example, trace its speaker and source links, then deliberately break a link to see what validation catches.
Requirements: Node.js 22 and the witness example download. This is a self-contained exercise; it does not require files from the preceding guides. Its people and statements are fictional, and its historical dates demonstrate how to preserve incomplete time information. It is not ELDÆON data.
1. Run the witness example
Extract the ZIP, open a terminal in the witness-accounts folder and run:
npm install --ignore-scripts
npm startThe archive pins @disclosureos/records and @disclosureos/schema to 2.0.0-beta.4. Its source.json records the source commit and file digests. The runner prints a full validation report; these are its success and check fields:
{
"success": true,
"checks": {
"structural": "passed",
"semantic": "passed",
"profile": "not_checked",
"external": "passed"
}
}The result confirms that the supplied JSON snapshots and their typed connections pass the checks. It does not authenticate the original interviews or establish that an account is accurate. The archive contains declarations about those interviews, not the original interview files.
Four files have different roles:
| File | What it holds |
|---|---|
observation.json | The source inventory: an interview recorded in 1981 and a retelling recorded in 1990. |
context.json | The reported event period, the third quarter of 1952. No exact event instant is invented. |
entities.json | Witness A, two accounts, a procedure declaration and a witness group. |
history.json | A separate review of Witness A's reported experience, tied to exact document snapshots. |
To use the validation calls in your own code, save this as check-accounts.mjs in the extracted folder:
import { readFileSync } from "node:fs";
import { createHash } from "node:crypto";
import {
parseResearchEntities,
parseResearchClaimHistory,
} from "@disclosureos/records/experimental/v2";
import { evaluateResearchClaimHistory } from "@disclosureos/schema/experimental/v2";
const read = (name) => readFileSync(name);
const entities = JSON.parse(read("entities.json").toString("utf8"));
const history = JSON.parse(read("history.json").toString("utf8"));
const local = [
parseResearchEntities(entities),
parseResearchClaimHistory(history),
];
if (local.some((result) => !result.success)) {
console.error(
JSON.stringify(
local.map((result) => result.issues),
null,
2,
),
);
process.exitCode = 1;
} else {
const documents = new Map(
["observation.json", "context.json", "entities.json"].map((name) => {
const bytes = read(name);
return [createHash("sha256").update(bytes).digest("hex"), bytes];
}),
);
const result = await evaluateResearchClaimHistory(history, { documents });
console.log(
JSON.stringify({ success: result.success, checks: result.checks }, null, 2),
);
if (!result.success) {
console.error(JSON.stringify(result.issues, null, 2));
process.exitCode = 1;
}
}Run node check-accounts.mjs. It prints the compact result shown above. The local parsers check structure and local relationships. The evaluator then checks the supplied snapshots, their identities and their connections to the observation. It fetches nothing from a URL.
2. Follow the witness and account links
Open entities.json. The witness has kind: "witness" and id: "A". The first account also uses id: "A", but has kind: "account". IDs are scoped by entity kind within the document: witness A and account A are different entities.
Both accounts identify the same speaker:
| Account ID | speakerWitnessIds value | recordedTime value | sourceDocument value |
|---|---|---|---|
A | ["A"] | Year 1981 | source:account-1981 |
retelling | ["A"] | Year 1990 | source:retelling-1990 |
The shared speaker link records a relationship. It does not establish independent corroboration. The review leaves multipleIndependent as unknown.
Each field contains a list of assertions. Here is the first account's speaker assertion, copied from the example:
{
"id": "speaker",
"content": {
"state": "known",
"value": ["A"],
"provenance": {
"sourceRef": "source:account-1981",
"attributedTo": "Synthetic source author",
"extractedBy": "Synthetic curator",
"locator": { "kind": "page", "page": 1 }
}
}
}known means a value was supplied. It does not mean the speaker's identity was independently verified. speaker identifies this assertion; A is the witness ID; source:account-1981 resolves to the observation's source inventory.
The account's content retains source wording. Its summary identifies summarizedBy, so an editor's wording does not become a witness quotation. recorder identifies who recorded the account. These roles can differ from provenance.extractedBy and provenance.attributedTo; do not infer one from another.
3. Try a broken speaker reference
Save this as break-speaker.mjs in the same folder. It changes only an in-memory copy, so the downloaded snapshots remain intact.
import { readFileSync } from "node:fs";
import { parseResearchEntities } from "@disclosureos/records/experimental/v2";
const entities = JSON.parse(readFileSync("entities.json", "utf8"));
const account = entities.entities.find(
(entity) => entity.kind === "account" && entity.id === "A",
);
account.fields.speakerWitnessIds[0].content.value = ["missing-witness"];
const result = parseResearchEntities(entities);
console.log(
JSON.stringify({ success: result.success, checks: result.checks }, null, 2),
);
console.error(JSON.stringify(result.issues, null, 2));
if (!result.success) process.exitCode = 1;Run node break-speaker.mjs. The structure passes, but success is false, semantic is failed, and the process exits with status 1. It prints this issue:
[
{
"code": "REF.ENTITY",
"stage": "semantic",
"pointer": "/entities/1/fields/speakerWitnessIds",
"message": "Typed local entity does not exist."
}
]The pointer locates the broken field in this fixture. An account can hold a speaker ID with the right shape and still fail because that witness does not exist. Run node check-accounts.mjs again to confirm the original files still pass.
A local parse does not resolve external source IDs or snapshot bytes. Those require the supplied-document check in step 1. Even a whitespace change to entities.json changes its SHA-256 and invalidates the history's saved reference. For a new review, build its document references, subject and assertion inputs from the new exact bytes. Keep earlier reviews bound to their original snapshots; changing a reference does not re-evaluate a claim.
Build your own witness and account document
Use the technical document kind research_entities with schema version 0.1.0. The same entity list supports four types; include only the ones your material needs.
| Entity | What to preserve |
|---|---|
| Witness | Approved public identity or pseudonym, role, qualifications and experience at a stated relevant time. |
| Account | Speaker IDs, recorder, recording time, source wording, attributed summary, citation and declared oath or custody context. |
| Procedure | The witness or account concerned, administration and reported results. A reported polygraph result does not validate an account. |
| Witness group | A sourced roster, reported count and aggregate description. A reported count need not equal the documented roster. |
Start with an Observation record containing the account's source entry. Add event and setting context when you need a reported event period or other context. Then:
- Set the entity document's
observationRefto the exact observation ID, schema ID and SHA-256. Declare any referenced context snapshots incontextRefs. - Create a witness using the public identity supplied by the source. Create a distinct account for each recording or retelling.
- Add sourced field assertions. Connect each account to its speakers through
speakerWitnessIdsand to its source throughsourceDocument. - Run
parseResearchEntities. After saving the document, compute its digest from the bytes you actually wrote. - If you need cross-document checks, declare that snapshot in a research claim history's
entityRefsand supply the original document bytes toevaluateResearchClaimHistory. An emptyclaimslist can check the connections without adding an assessment.
The field reference exposes all four entity types, their nested fields and exact allowed values. Package version 2.0.0-beta.4, entity document version 0.1.0 and research claim-history version 0.3.0 identify different things.
Preserve time, uncertainty and privacy
Keep the dates separate. This example reports an event in the third quarter of 1952, accounts recorded in 1981 and 1990, and declarations assembled in 2026. Five years of aviation experience applies to the event quarter. It is not a current credential. Qualifications use a calendar time, an exact context reference or an explicitly unknown relevant time.
Keep missing states separate. Omission means a field was not supplied. unknown carries a reason; redacted represents withheld information. approximate qualifies a value and unmapped retains wording not mapped to a supported value. Zero and false are actual values, not defaults. Alternative assertions may coexist. Use an explicit selection, with its rationale, if a reviewer chooses one.
Use approved public information. Private identity mappings and contact properties are excluded from typed public fields. Free text, URLs and extensions are not automatically sanitized. A pseudonym or anonymous: true does not remove identifying details elsewhere. Keep private intake data outside public documents.
The example leaves consent, oath, procedure results and independence unknown. A public pseudonym does not imply consent to publish a legal name. Neither anonymity nor a missing field supplies a credibility penalty.
Review an exact declaration
A research claim history (0.3.0) supports observation, measurement, context and research-entity subjects. Use subject to identify what is being reviewed, and entityInputRefs to identify supporting assertions. Each reference binds a document ID, schema ID and SHA-256 to a typed target { kind, id, field? }; an assertion input also identifies its assertionId.
In the example, the review concerns witness A → experienceYears. Its input is assertion experience on that field. The reviewer records the outcome as reported, explains the rationale and leaves independence unknown. It is a review of that sourced qualification, not a verdict on the person or the entire event.
witnessReview belongs to an assessed claim with an entity subject, explicit inputs, evaluator, method/version, evaluation time and rationale. Ratings, factors, consistency and independence remain reviewer declarations. Qualitative ratings do not become numeric scores. Competing reviews can coexist; changing the snapshot changes the subject under review.
A passing supplied-document result leaves source-artifact integrity, scientific interpretation, temporal normalization, sensor fusion and profile applicability unchecked. Continue with Claims for assessment outcomes and review history.
Check source-file provenance
This is a separate workflow for verifying selected account files against their declared digests and recording citations. The API is named historical-testimony, but contemporary accounts can use it too. It accepts unknown event time and location and does not require measurements, assessments or a legal name for the speaker.
Use its own example and contract. This profile accepts claim-history 0.1.0, not the 0.3.0 history above. Do not remove witness or account information to make a research history fit it. The shared provenance guide supplies a compatible runnable example and local source bytes.
To assemble this check:
- Put the preserved account in the observation's
sourcesinventory withkind: testimony, its actual access declaration and a SHA-256 digest. - Supply a testimony selection with the exact
historyIdandobservationId. Each selected account names asourceRefand recording citation. - Supply the account bytes in an
assetsmap keyed by the source reference. The evaluator does not fetch source URLs. - For direct extractions, retain the passage locator, extractor and speaker attribution in the current source statement or observation assertion. Mixed-speaker material needs passage-specific attribution.
Known recording context has recording.recordedBy and recording.reference. Unknown context has a reason; it can be represented but does not satisfy the required citation. The recorder, extractor and attributed speaker are separate identities. A document containing testimony may remain a document source; do not relabel it just to pass this profile.
| Result | Meaning |
|---|---|
| Missing required declaration | The corresponding requirement fails. |
| Eligible declarations, unavailable bytes | The file check remains not_checked. |
| Supplied bytes differ from the digest | The file check fails. |
| No supplied extraction | Extraction checks are not_applicable; transcription completeness is not established. |
| Matching nonempty bytes and required declarations | The selected-file checks pass within their stated scope. |
Source title and URI are recommended citation aids, not scientific scoring factors. Superseded statements do not determine the current extraction checklist. OCR, translations and other derived material need their own processing provenance.
A passing result does not establish speaker identity, recording authenticity, account accuracy, firsthand knowledge, witness independence, extraction accuracy, assessment support, redistribution rights or scientific eligibility. The returned history retains its access declarations; it is not a public projection.
Continue exploring
- Witness and account fields: browse the entity document, attributed reviews and source-file selection separately.
- Accounts in the showcase: explore the full fictional case's accounts and recording context. Its sources differ from this historical exercise.
- Documents and custody: preserve editions, release context and the record of how material was handled.
Exact schema exports are @disclosureos/records/experimental/v2/entities/schema/0.1.0, @disclosureos/records/experimental/v2/claims/schema/0.3.0 and @disclosureos/schema/experimental/v2/historical-testimony/schema. Their URNs identify contracts, not downloadable HTTP endpoints.