TRD — Systems Architecture Spec — Machine-Readable System Contract (SAS v0.1)¶
Initiative: INIT-SYSTEMSARCHITECTURESPEC-001
Architecture Overview¶
Go structs (source of truth)
│ invopop/jsonschema
▼
JSON Schema ── schemakit lint --property-case camelCase ── //go:embed
│
▼
Canonical JSON (interchange contract)
│
├──► Zod schemas + TypeScript types (conformance target)
├──► Renderers: Mermaid / D2 / Graphviz DOT
├──► Semantic diff → Change Impact IR → compliance profiles
└──► CALM import/export adapters
JSON Schema validates the wire contract; it must never become a second type system. The generated schema stays within the PlexusOne static-type profile: no oneOf/anyOf/allOf/not/conditional schemas, homogeneous arrays, deterministic property types, explicit discriminated fields for polymorphism.
Core Graph Model¶
type Architecture struct {
Version string `json:"version"`
Metadata Metadata `json:"metadata,omitempty"`
Nodes []Node `json:"nodes"`
Relationships []Relationship `json:"relationships"`
Boundaries []Boundary `json:"boundaries,omitempty"`
Bindings []ProtocolBinding `json:"bindings,omitempty"`
Views []View `json:"views,omitempty"`
}
Node¶
type Node struct {
ID string `json:"id"`
Kind NodeKind `json:"kind"` // e.g. "service", "gateway", "compute.function", "data.database", "agent", "mcp_server"
Name string `json:"name"`
Owner string `json:"owner,omitempty"`
Technology *Technology `json:"technology,omitempty"` // {provider: "aws", service: "lambda"}
Identity *Identity `json:"identity,omitempty"`
Boundaries []string `json:"boundaries,omitempty"` // IDs; multi-membership, no single hierarchy
Criticality string `json:"criticality,omitempty"` // tier0, tier1, ...
Assurance *Assurance `json:"assurance,omitempty"`
Refs []ExternalRef `json:"refs,omitempty"` // OpenAPI, Multi-Agent Spec, Threat Model Spec, IaC
Extensions Extensions `json:"extensions,omitempty"` // namespaced typed maps (security, sre, agent, compliance)
}
Kind taxonomy is a dotted vocabulary (compute.function, compute.instance, data.database, messaging.queue, gateway, service, agent, mcp_server, external_service, actor), deliberately small; concrete products come from Technology + catalogs, never new core kinds.
Relationship¶
The edge is the security-critical object. It must be able to express what most diagram formats collapse into an arrow:
type Relationship struct {
ID string `json:"id"`
From string `json:"from"`
To string `json:"to"`
Kind RelationKind `json:"kind"` // calls | data.access | data_flow | publishes_to | subscribes_to | authenticates_to | authorizes_via | observes | enforces | depends_on | spawns | delegates_to
Transport *Transport `json:"transport,omitempty"` // protocol, port, applicationProtocol, encryption
Operations []Operation `json:"operations,omitempty"` // generic verb vocabulary
Identity *IdentityRef `json:"identity,omitempty"` // which identity the caller acts as
Authorization *Authorization `json:"authorization,omitempty"` // entitlements
Data *DataFlow `json:"data,omitempty"` // classifications, e.g. customer_data, source_code
CrossesBoundaries []string `json:"crossesBoundaries,omitempty"` // derived + assertable
CriticalPath bool `json:"criticalPath,omitempty"`
Sync string `json:"sync,omitempty"` // sync | async
Assurance *Assurance `json:"assurance,omitempty"`
ProtocolRef string `json:"protocolRef,omitempty"` // e.g. "pidl://oauth/dpop-authorization-code"
Extensions Extensions `json:"extensions,omitempty"`
}
Operations vocabulary: generic verbs discover | read | create | update | delete | execute | administer with catalog-supplied specialization mappings (HTTP GET → read; SQL SELECT → read; MCP invoke_tool → execute; Agent spawn/delegate → execute). Views choose the abstraction level; the model stores both when the author supplies specifics.
Boundary¶
type Boundary struct {
ID string `json:"id"`
Kind string `json:"kind"` // network | trust | environment | account | region | compliance | organization
Name string `json:"name"`
Attributes map[string]string `json:"attributes,omitempty"` // e.g. vpc: prod, classification: restricted
Compliance *ComplianceBoundary `json:"compliance,omitempty"` // e.g. fedrampBoundary: true, controls: ["SC-7", ...]
Extensions Extensions `json:"extensions,omitempty"`
}
Nodes reference boundaries by ID (many-to-many). "Crossing" is computable: an edge crosses boundary B when exactly one endpoint is a member.
Identity & Entitlement¶
First-class, not buried in attributes:
type Identity struct {
Type string `json:"type"` // human | workload | service | agent | device | external
Mechanism string `json:"mechanism,omitempty"` // aws_iam_role | spiffe | oauth_client | oidc | aauth | k8s_service_account | x509 | api_key
Ref string `json:"ref,omitempty"`
}
type Entitlement struct {
Subject string `json:"subject"`
Action string `json:"action"`
Resource string `json:"resource"`
Conditions []string `json:"conditions,omitempty"`
}
This maps naturally onto IAM systems and OpenFGA/SpiceDB tuples later, without making ReBAC part of the core.
ProtocolBinding (PIDL integration)¶
PIDL models protocols with abstract participants; SAS instantiates them:
type ProtocolBinding struct {
ID string `json:"id"`
ProtocolRef string `json:"protocolRef"` // "pidl://oauth/dpop-authorization-code"
Participants map[string]string `json:"participants"` // pidl participant → SAS node ID
}
The same PIDL OAuth/AAuth/MCP definition binds into many architectures. SAS never duplicates choreography; a lightweight Flow concept is deliberately omitted from v0.1 core in favor of ProtocolBinding + protocolRef on edges.
External References & Extensions¶
type ExternalRef struct {
Type string `json:"type"` // openapi | multi-agent-spec | threat-model-spec | pidl | terraform | otel
Ref string `json:"ref"`
}
Extensions is a map of namespace → typed extension object (security, sre, compliance, agent). Extension shapes are declared as Go types per namespace — explicit fields, not schema composition. This is the SAS answer to CALM's allOf-based extension model.
Assurance (evidence references only in v0.1)¶
type Assurance struct {
Tests []string `json:"tests,omitempty"` // test://integration/orders-db-authz
Metrics []string `json:"metrics,omitempty"` // otel://service/orders-api
Detections []string `json:"detections,omitempty"` // siem://rule/DB-UNEXPECTED-WRITE
Deployment []string `json:"deployment,omitempty"` // aws://lambda/orders-api
}
SAS stores pointers to evidence, never the evidence itself. Coverage reporting (which declared elements lack tests/metrics/detections) is computable from the graph; reconciliation against live evidence is out of scope for v0.1.
Views & Profiles¶
A view is a query, not a diagram file:
type View struct {
ID string `json:"id"`
Name string `json:"name"`
IncludeKinds []string `json:"includeKinds,omitempty"`
IncludeRelations []string `json:"includeRelations,omitempty"`
IncludeBoundaries []string `json:"includeBoundaries,omitempty"`
GroupBy string `json:"groupBy,omitempty"` // boundary kind to nest by
Level string `json:"level,omitempty"` // generic | specific (operations abstraction)
}
A profile declares which optional semantics become mandatory (validation, not schema variation):
| Profile | Requires |
|---|---|
| development | nodes, relationships |
| deployment | technology, boundaries (account/region/network), transport |
| security | identity on boundary-crossing edges, encryption, operations, entitlements on writes |
| threat-model | data classifications, boundary membership, external flags |
| sre | criticality, owner, availability target on tier0/tier1 |
| fedramp | compliance boundary membership, control mappings on boundary components |
Profile checks are application-level validation rules over one schema — the schema itself never branches.
Validation Rules Engine¶
Rule examples shipped in v0.1:
tier0 node → must have owner, availability target, assurance.metrics
boundary crossing → must define transport.encryption and identity (security profile)
write operations → must define authorization.entitlements (security profile)
external node edge → must declare data classifications (threat-model profile)
internet-facing → public endpoints must define authn, TLS, owner,
data classification (launch-readiness rules)
dangling references → node/boundary/binding IDs must resolve
Rules are data-driven where practical (rule ID, severity, profile, predicate) so profiles compose. Launch-readiness rules are the go-live gate for portfolio web apps: sas validate --profile security must pass before a vertical app ships to production.
Threat Model Spec Bridge¶
SAS supplies the system-under-analysis that grokify/threat-model-spec needs, so threat models never re-describe the system:
SAS architecture ──► sas export threat-model ──► system-under-analysis
├── components (nodes)
├── trust-boundary crossings
├── data flows + classifications
├── identities per edge
└── external dependencies
The bridge exports facts only; threats, mitigations, and risk reasoning stay in Threat Model Spec, which references SAS IDs (sas_resource, sas_relationship). Dogfooded on the first live-site launches (vertical portfolio apps).
Semantic Diff & Change Impact (ships post-v0.1)¶
The IR must stay diff-expressible from day one (the PRD expressiveness test), but the engine ships in v0.2.
architecture@baseline ──┐
├──► sas diff ──► ChangeSet (typed) ──► ChangeImpact ──► profile classification
architecture@proposed ──┘
- ChangeSet: typed changes — NodeAdded/Removed/Modified, RelationshipAdded/..., BoundaryMembershipChanged, field-level deltas (authentication mtls→oauth, operations read→read+write, internal→external target).
- ChangeImpact: semantic classification —
NEW_EXTERNAL_DEPENDENCY,AUTHN_MECHANISM_CHANGED,ENTITLEMENT_EXPANSION,NEW_BOUNDARY_CROSSING,DATA_CLASSIFICATION_CHANGE,COMPUTE_MODEL_CHANGED, plus affected boundaries and (via mappings) potentially affected controls. - Compliance profiles consume ChangeImpact: the FedRAMP profile emits
NO_REVIEW_REQUIRED | CHANGE_ASSESSMENT_REQUIRED | POTENTIAL_SIGNIFICANT_CHANGEwith the triggering changes as evidence. The tool produces decision support; classification as an actual significant change is a recorded human decision (Assessment{id, decision, reviewer, evidence}). - Baselines: an approved baseline is a named, immutable architecture version; releases reference
{baseline, proposed, assessment}.
Renderers¶
Renderer = pure function (Architecture, View) → string. v0.1 targets: Mermaid (GitHub-native), D2 (best generated-diagram ergonomics), Graphviz DOT (auto-layout for large graphs). Structurizr/C4 and draw.io are explicitly later. Catalogs provide display labels/icon hints (AWS Lambda icon for aws/lambda).
CALM Interop (out of scope; demand-driven)¶
CALM (FINOS) can never be the foundation: its core uses anyOf (node-type-definition) and recommends allOf for extension, which violates the static-type profile. Adapters are not scheduled in this initiative — CALM adoption is still limited and finance-domain-centric, so sas import calm / sas export calm get scoped as new work only when a real consumer asks. If built: mapping is straightforward at the instance level (Node↔Node, Relationship↔Relationship, Boundary↔deployed-in/composition, Controls↔compliance extension); lossy conversions are reported, never silent; core never depends on CALM shapes.
CLI¶
# v0.1
sas validate <arch.json> [--profile security,threat-model]
sas view <arch.json> --view security --format d2|mermaid|dot
sas bind <arch.json> --pidl <protocol.json> # verify bindings resolve
sas export threat-model <arch.json> # system-under-analysis for threat-model-spec
sas assure <arch.json> # assurance-reference coverage report
# v0.2+
sas diff <base.json> <proposed.json> [--profile fedramp] [--format json]
# demand-driven (not scheduled)
sas import calm <calm.json> / sas export calm <arch.json>
Library-first: all capability in importable packages; cmd/sas is a thin Cobra adapter.
Repository Layout¶
systems-architecture-spec/
├── sas/ # core types: architecture, node, relationship, boundary, identity, view, binding
├── validate/ # rules engine + profiles
├── diff/ # semantic diff, change impact, assessments
├── render/
│ ├── mermaid/
│ ├── d2/
│ └── dot/
├── catalog/ # technology catalogs (aws, gcp, k8s) + operation mappings
├── bridge/
│ └── threatmodel/ # system-under-analysis export for threat-model-spec
├── schema/ # generated *.schema.json + //go:embed + gen/ (go:build ignore) + tools.go
├── ts/ # Zod schemas + TS types + shared fixtures runner
├── cmd/sas/ # cobra CLI
├── examples/ # real architectures (dogfood) + valid/invalid fixture corpus
└── docs/specs/initiatives/INIT-SYSTEMSARCHITECTURESPEC-001/
Conformance & Testing¶
- Shared fixture corpus (
examples/fixtures/{valid,invalid}) exercised by the Go validator, JSON Schema validation, and Zod — behavioral conformance, not just type generation. - Round-trip test: Go → JSON → (schema validate) → Zod parse → JSON → Go; semantic equality required.
schemakit lint --property-case camelCasein CI, default profile, matching established org practice (deppolicy). Thescaleprofile additionally rejects any use ofadditionalPropertiesas a value schema — including the strict, homogeneousmap[string]stringonBoundary.Attributes— which is a stronger bar than the static-type-friendly principle requires: the principle bans dynamic, heterogeneous, ambiguous shapes (map[string]any, unions), not a fixed-value-type map that Go, Rust, and TypeScript (Record<string,string>) all represent identically and unambiguously.tools.gowith//go:build toolsblank imports sogo mod tidykeeps the schema generator's deps (standard gen/main.go pattern).- TypeScript/Zod generation follows the proven
multi-agent-specpipeline:json-schema-to-zodper$def(topologically ordered, refs inlined for self-contained output) plus one generation pass for the root document type, keyed by name (ArchitectureSchema) sinceinvopop/jsonschema'sExpandedStruct: trueinlines the root type's own properties rather than emitting it as a named$def.
Dependencies¶
Cobra (CLI), invopop/jsonschema (schema generation), grokify/schemakit (lint, dev/CI tool). Verify latest versions before adding. No runtime dependency on PIDL/Threat Model Spec/Multi-Agent Spec — references are by URI; optional typed helpers may import their types later.