Skip to main content

Layer Permissions

SOMA enforces strict write permissions across its four knowledge layers. Every writeToLayer call is checked against a static permission matrix, and every entity is validated for layer-specific required fields before being persisted. This page is the authoritative reference for what is allowed, what is required, and what happens when rules are violated.


Permission Matrix​

Seven workers, four layers. Each cell indicates the access level.

WorkerL1 ArchiveL2 Working MemoryL3 Emerging KnowledgeL4 Canon
HarvesterWRITE———
ReconcilerWRITE———
Synthesizer——WRITE—
Cartographer——WRITE—
Team-Context—WRITE——
Governance———WRITE
Policy BridgeREADREADREADREAD

Key constraints:

  • Workers can only write to their designated layer. A Harvester attempting to write to L3 is rejected.
  • Policy Bridge has read-only access to all layers. It never creates or modifies entities.
  • Governance is the sole writer to L4. There is no automated path to canon.
  • L1 has two writers (Harvester and Reconciler) because the Reconciler auto-fixes structural issues in archived entries.
  • L3 has two writers (Synthesizer and Cartographer) because both generate machine proposals — the Synthesizer from content patterns and the Cartographer from relationship and contradiction detection.

Required Fields per Layer​

Each layer requires specific fields beyond the common entity fields. writeToLayer calls validateLayerFields before persisting.

L1 — Execution Archive​

FieldTypeConstraint
layer'archive'Must be exactly 'archive'
source_workerWorkerNameMust be 'harvester' or 'reconciler'

L1 entries must not have a decay_at field. L1 is permanent storage — raw traces and decayed entries live here indefinitely.

L2 — Team Working Memory​

FieldTypeConstraint
layer'working'Must be exactly 'working'
source_workerWorkerNameMust be 'team-context'
team_idstringMust be non-empty. Scopes the entry to a specific team.
decay_atstring (ISO 8601)Required. Default: 14 days from creation. Configurable per team via DecayConfig.teamDecayDays.

L3 — Emerging Knowledge​

FieldTypeConstraint
layer'emerging'Must be exactly 'emerging'
source_workerWorkerNameMust be 'synthesizer' or 'cartographer'
confidence_scorenumberRequired. Must be between 0.0 and 1.0 inclusive.
evidence_linksstring[]Required. Array of L1 entity IDs that support this proposal.
decay_atstring (ISO 8601)Required. Default: 90 days from creation. Configurable via DecayConfig.l3DefaultDays.

Confidence scoring rules:

  • Cross-agent corroboration (5+ agents): >= 0.8
  • Single-agent patterns: capped at 0.5
  • Evidence count contributes incrementally (+0.02 per trace, +0.15 per agent)

L4 — Institutional Canon​

FieldTypeConstraint
layer'canon'Must be exactly 'canon'
source_workerWorkerNameMust be 'governance'
ratified_bystringRequired. Reviewer ID who approved the promotion.
ratified_atstring (ISO 8601)Required. Timestamp of the promotion action.
origin_l3_idstringRequired. ID of the source L3 entry. Creates traceability: L4 -> L3 -> L1 evidence chain.

L4 entries must not have a decay_at field. Canon is permanent — ratified organizational truth does not expire.


Validation Rules​

These rules are enforced by validateLayerFields and the vault's update() method.

Confidence Score​

The confidence_score field on L3 entries must be a number between 0.0 and 1.0 inclusive. Values outside this range cause a validation error. This is enforced at write time, not just at synthesis time, so manually created L3 entries are also validated.

confidence_score: -0.1  -> validation error
confidence_score: 0.0 -> valid
confidence_score: 0.85 -> valid
confidence_score: 1.0 -> valid
confidence_score: 1.5 -> validation error

Decay Prohibition on L1 and L4​

L1 (Archive) and L4 (Canon) entries must not have a decay_at field. Archive is permanent historical storage; canon is ratified truth. If a decay_at field is present on an L1 or L4 entity, validateLayerFields rejects the write.

layer: 'archive', decay_at: '2026-06-01'  -> validation error
layer: 'canon', decay_at: '2026-06-01' -> validation error
layer: 'working', decay_at: '2026-06-01' -> valid (required)
layer: 'emerging', decay_at: '2026-06-01' -> valid (required)

Team ID Non-Empty​

The team_id field on L2 entries must be a non-empty string. An empty string or missing value causes a validation error. This ensures every working memory entry is properly scoped to a team.

layer: 'working', team_id: 'backend-team'  -> valid
layer: 'working', team_id: '' -> validation error
layer: 'working', team_id: undefined -> validation error

Layer Immutability​

The vault.update() method rejects any attempt to change the layer field on an existing entity. Entities cannot move between layers via direct update. The only supported layer transitions are:

  1. Governance promotion: promote() creates a new L4 entity from an L3 source. The original L3 entry's status changes to promoted, but its layer stays emerging.
  2. Decay: The decay processor creates a new L1 entity and removes the expired L2/L3 entry. The L1 entry gets a decayed_from field indicating origin.

Both transitions create new entities rather than modifying the layer field in place.


Error Types​

LayerPermissionError​

Thrown when a worker attempts to write to a layer outside its permission scope.

class LayerPermissionError extends Error {
worker: WorkerName;
layer: KnowledgeLayer;
}

Example triggers:

// Harvester trying to write to L3
writeToLayer(vault, 'emerging', 'harvester', entity);
// -> LayerPermissionError: Worker 'harvester' cannot write to layer 'emerging'

// Synthesizer trying to write to L4
writeToLayer(vault, 'canon', 'synthesizer', entity);
// -> LayerPermissionError: Worker 'synthesizer' cannot write to layer 'canon'

// Any worker trying to write to L4 except governance
writeToLayer(vault, 'canon', 'cartographer', entity);
// -> LayerPermissionError: Worker 'cartographer' cannot write to layer 'canon'

Validation Errors​

Thrown by validateLayerFields when required fields are missing or invalid.

// Missing confidence_score on L3 entry
writeToLayer(vault, 'emerging', 'synthesizer', {
type: 'insight',
name: 'retry-pattern',
evidence_links: ['trace-1'],
decay_at: '2026-06-19T00:00:00Z',
});
// -> Error: L3 entry requires confidence_score

// Missing team_id on L2 entry
writeToLayer(vault, 'working', 'team-context', {
type: 'insight',
name: 'sprint-context',
decay_at: '2026-04-04T00:00:00Z',
});
// -> Error: L2 entry requires team_id

// decay_at on L4 entry
writeToLayer(vault, 'canon', 'governance', {
type: 'policy',
name: 'retry-limit',
ratified_by: 'alice',
ratified_at: '2026-03-21T00:00:00Z',
origin_l3_id: 'proposal-123',
decay_at: '2026-12-01T00:00:00Z',
});
// -> Error: L4 entries must not have decay_at

Layer Change Rejection​

Thrown by vault.update() when the layer field is included in the update payload.

await vault.update('entity-123', { layer: 'canon' });
// -> Error: Layer field cannot be changed via update

This prevents circumventing the governance pipeline. The only path from L3 to L4 is through promote().


writeToLayer Flow​

The complete execution flow when writeToLayer is called:

  1. Permission check — enforceWritePermission(worker, layer) verifies the worker appears in the permission matrix for the target layer. Throws LayerPermissionError on failure.

  2. Field injection — The layer and source_worker fields are set on the entity object automatically, overriding any values the caller may have provided.

  3. Field validation — validateLayerFields(layer, entity) checks all layer-specific required fields:

    • L1: layer + source_worker present; no decay_at
    • L2: above + team_id (non-empty) + decay_at
    • L3: above + confidence_score (0-1) + evidence_links + decay_at
    • L4: above + ratified_by + ratified_at + origin_l3_id; no decay_at
  4. Disk space check — statfsSync verifies at least 10MB of free space. Rejects with a clear error if insufficient.

  5. Lock acquisition — Acquires _vault.lock with O_EXCL flag. Times out after 5 seconds with 50ms retry intervals. Stale locks (PID not running) are auto-removed.

  6. Entity creation — Writes the YAML frontmatter + Markdown body to a .md file in the appropriate type subdirectory.

  7. Index update — Adds the entity to _index.json with layer included for fast filtering.

  8. Mutation log — Appends a create record to _mutations.jsonl with timestamp and affected fields.

  9. Lock release — Removes _vault.lock.

  10. Return — Returns the fully populated Entity object with all generated fields (id, created, updated).

If any step after lock acquisition fails, the lock is released in a finally block and any temp files are cleaned up. No partial writes are left on disk.