Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,41 @@ This repo defines **contracts** and **conformance** for workstation/CI lanes. It

If you need to *run* a lane, you’re looking for the workspace controller / runner repo, not this one.

## Agent Machine and Office Plane conformance

This repo now includes conformance fixtures for SourceOS Agent Machine scoped mounts and Prophet Workspace Office Plane dry-run behavior.

Good fixture:

```text
conformance/good/agent-machine-office-dry-run.json
```

Bad fixtures:

```text
conformance/bad/agent-machine-whole-home-mount.json
conformance/bad/agent-machine-unscoped-downloads.json
conformance/bad/office-raw-apple-app-db.json
```

The semantic validator rejects:

- `$HOME` / `~` as a whole-home Agent Machine mount root;
- unscoped `~/Downloads` browser download mounts;
- raw Apple app database/library access for Notes, Photos, Reminders, or Voice Memos;
- Agent Machine lanes that do not emit `agent-machine.mount.evidence`;
- Office Plane lanes that do not emit `office.artifact.evidence`.

These checks preserve the intended boundary:

```text
sourceosctl agent-machine mounts plan -> scoped mount evidence
sourceosctl office ... -> OfficeArtifact-compatible dry-run/evidence
agent-term office ... -> governance-preserving operator event
AgentPlane -> AgentMachineMountEvidence / OfficeArtifactEvidence
```

## IPC v0 reference harness

This repo now includes a small IPC v0 reference harness under:
Expand Down Expand Up @@ -68,6 +103,8 @@ The production runner/orchestrator remains out of scope for this repo. The refer
- Implementing the production runner/orchestrator (execution belongs elsewhere)
- Hosting container images (this repo only **pins** digests once published)
- Being a monorepo for all workstation tooling (we stay small and auditable)
- Executing Agent Machine mounts or Office generation/conversion directly
- Mounting raw host app databases such as Apple Notes, Photos, Reminders, or Voice Memos

## How this plugs into the platform

Expand Down
35 changes: 35 additions & 0 deletions conformance/bad/agent-machine-unscoped-downloads.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"api_version": "workstation-contracts.socios.io/v0.1",
"kind": "WorkstationContract",
"metadata": {
"id": "agent-machine-unscoped-downloads",
"name": "Invalid Agent Machine unscoped downloads mount",
"description": "This fixture must fail because browser downloads must use ~/Downloads/SourceOS/agent-downloads, not the whole host Downloads directory.",
"labels": {
"sourceos.agent_machine": "true"
}
},
"spec": {
"lanes": [
{
"name": "invalid-unscoped-downloads",
"backend": {
"type": "container",
"container": {
"image": "ghcr.io/sourceos-linux/workstation-contracts-truth-lane@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"workdir": "/workspace"
}
},
"steps": [
{
"name": "bad-downloads-mount",
"run": "sourceosctl agent-machine mounts plan --dev-root ~/dev --docs-root ~/Documents/SourceOS/agent-output --downloads-root ~/Downloads"
}
],
"evidence": {
"emit": ["agent-machine.mount.plan"]
}
}
]
}
}
35 changes: 35 additions & 0 deletions conformance/bad/agent-machine-whole-home-mount.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"api_version": "workstation-contracts.socios.io/v0.1",
"kind": "WorkstationContract",
"metadata": {
"id": "agent-machine-whole-home-mount",
"name": "Invalid Agent Machine whole home mount",
"description": "This fixture must fail because Agent Machine must never mount $HOME wholesale.",
"labels": {
"sourceos.agent_machine": "true"
}
},
"spec": {
"lanes": [
{
"name": "invalid-whole-home-mount",
"backend": {
"type": "container",
"container": {
"image": "ghcr.io/sourceos-linux/workstation-contracts-truth-lane@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"workdir": "/workspace"
}
},
"steps": [
{
"name": "bad-whole-home-mount",
"run": "sourceosctl agent-machine mounts plan --dev-root $HOME --docs-root ~/Documents/SourceOS/agent-output --downloads-root ~/Downloads/SourceOS/agent-downloads"
}
],
"evidence": {
"emit": ["agent-machine.mount.plan"]
}
}
]
}
}
35 changes: 35 additions & 0 deletions conformance/bad/office-raw-apple-app-db.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"api_version": "workstation-contracts.socios.io/v0.1",
"kind": "WorkstationContract",
"metadata": {
"id": "office-raw-apple-app-db",
"name": "Invalid raw Apple app database access",
"description": "This fixture must fail because Notes, Photos, Reminders, and Voice Memos must be App Doors, not default raw database mounts.",
"labels": {
"sourceos.office_plane": "true"
}
},
"spec": {
"lanes": [
{
"name": "invalid-raw-apple-app-db",
"backend": {
"type": "container",
"container": {
"image": "ghcr.io/sourceos-linux/workstation-contracts-truth-lane@sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
"workdir": "/workspace"
}
},
"steps": [
{
"name": "bad-raw-notes-db-access",
"run": "sourceosctl office inspect ~/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
}
],
"evidence": {
"emit": ["office.artifact.evidence"]
}
}
]
}
}
60 changes: 60 additions & 0 deletions conformance/good/agent-machine-office-dry-run.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"api_version": "workstation-contracts.socios.io/v0.1",
"kind": "WorkstationContract",
"metadata": {
"id": "agent-machine-office-dry-run",
"name": "Agent Machine and Office Plane dry-run conformance lane",
"description": "Conformance fixture for SourceOS Agent Machine scoped mounts and Prophet Workspace Office Plane dry-run behavior.",
"labels": {
"sourceos.capability": "agent-machine-office",
"sourceos.agent_machine": "true",
"sourceos.office_plane": "true"
}
},
"spec": {
"lanes": [
{
"name": "agent-machine-office-dry-run",
"backend": {
"type": "container",
"container": {
"image": "ghcr.io/sourceos-linux/workstation-contracts-truth-lane@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"workdir": "/workspace"
}
},
"steps": [
{
"name": "plan-scoped-agent-machine-mounts",
"run": "sourceosctl agent-machine mounts plan --dev-root ~/dev --docs-root ~/Documents/SourceOS/agent-output --downloads-root ~/Downloads/SourceOS/agent-downloads"
},
{
"name": "inspect-office-backends",
"run": "sourceosctl office doctor"
},
{
"name": "plan-office-deck-generation",
"run": "sourceosctl office generate --dry-run --artifact-type slide-deck --format pptx --title 'Demo Briefing Deck' --workroom-id workroom-demo-0001"
},
{
"name": "plan-office-pdf-conversion",
"run": "sourceosctl office convert /workspace/output/demo.docx --to pdf --dry-run --workroom-id workroom-demo-0001"
},
{
"name": "record-agentterm-office-event",
"run": "agent-term office create-deck '!prophet-workspace' --workroom workroom-demo-0001 --title 'Demo Briefing Deck'"
}
],
"evidence": {
"emit": [
"env.fingerprint",
"agent-machine.mount.plan",
"agent-machine.mount.evidence",
"office.artifact.plan",
"office.artifact.evidence",
"agentterm.office.event"
]
}
}
]
}
}
50 changes: 50 additions & 0 deletions tools/validate_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,38 @@
DIGEST_RE = re.compile(r"@sha256:[0-9a-f]{64}$")
SENTINEL = "@sha256:REPLACE_WITH_DIGEST"

FORBIDDEN_RUN_PATTERNS = [
(
re.compile(r"sourceosctl\s+agent-machine\s+mounts\s+plan[^\n]*--dev-root\s+(?:\$HOME|~)(?:\s|$)"),
"Agent Machine must not use $HOME or ~ as the dev/code root; use ~/dev or an explicit repo allowlist root.",
),
(
re.compile(r"sourceosctl\s+agent-machine\s+mounts\s+plan[^\n]*--docs-root\s+(?:\$HOME|~)(?:\s|$)"),
"Agent Machine must not use $HOME or ~ as the document root; use ~/Documents/SourceOS/agent-output.",
),
(
re.compile(r"sourceosctl\s+agent-machine\s+mounts\s+plan[^\n]*--downloads-root\s+~/Downloads(?:\s|$)"),
"Browser downloads must use scoped ~/Downloads/SourceOS/agent-downloads, not the whole host Downloads directory.",
),
(
re.compile(r"sourceosctl\s+office\s+inspect\s+[^\n]*(?:NoteStore\.sqlite|group\.com\.apple\.notes|Photos\.photoslibrary|Voice\s*Memos|VoiceMemos|Reminders)", re.IGNORECASE),
"Office/App integrations must use future App Doors; raw Apple app databases/libraries must not be inspected or mounted by default.",
),
]

REQUIRED_EVIDENCE_BY_LABEL = {
"sourceos.agent_machine": "agent-machine.mount.evidence",
"sourceos.office_plane": "office.artifact.evidence",
}

def load_json(p: Path):
return json.loads(p.read_text(encoding="utf-8"))

def iter_steps(doc):
for lane in doc["spec"]["lanes"]:
for step in lane.get("steps", []):
yield lane, step

def main():
if len(sys.argv) < 2:
print("Usage: tools/validate_contract.py <contract.json> [<contract2.json>...]", file=sys.stderr)
Expand All @@ -33,6 +62,8 @@ def main():
print(f"FAIL schema: {p}: {e.message}", file=sys.stderr)
continue

labels = doc.get("metadata", {}).get("labels", {})

for lane in doc["spec"]["lanes"]:
b = lane["backend"]
if b["type"] == "container":
Expand All @@ -47,6 +78,25 @@ def main():
ok = False
print(f"FAIL semantic: {p}: lane '{lane['name']}' container.image must be digest-pinned (@sha256:<64hex>)", file=sys.stderr)

evidence_emit = set(lane.get("evidence", {}).get("emit", []))
for label, required_evidence in REQUIRED_EVIDENCE_BY_LABEL.items():
if labels.get(label) == "true" and required_evidence not in evidence_emit:
ok = False
print(
f"FAIL semantic: {p}: lane '{lane['name']}' must emit {required_evidence} when {label}=true",
file=sys.stderr,
)

for lane, step in iter_steps(doc):
run = step.get("run", "")
for pattern, message in FORBIDDEN_RUN_PATTERNS:
if pattern.search(run):
ok = False
print(
f"FAIL semantic: {p}: step '{step.get('name', '<unnamed>')}' in lane '{lane['name']}': {message}",
file=sys.stderr,
)

if ok:
print("OK: all contracts valid")
return 0
Expand Down
Loading