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
SynthesizerWRITE
CartographerWRITE
Team-ContextWRITE
GovernanceWRITE
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 checkenforceWritePermission(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 validationvalidateLayerFields(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 checkstatfsSync 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.