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
17 changes: 17 additions & 0 deletions contracts/examples/applied-fix.valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "urn:srcos:workstation-action-boundary:applied-fix-shell-20260718",
"specVersion": "0.1.0",
"artifact_kind": "applied_fix",
"requested_action": "fix shell",
"mutation_planned": true,
"mutation_performed": true,
"performed_by": "sourceos-cli/mdheller",
"policy_decision_refs": ["urn:srcos:policy-decision:fix-shell-admission-20260718"],
"before_refs": ["urn:srcos:evidence:shell-config-before-20260718"],
"after_refs": ["urn:srcos:evidence:shell-config-after-20260718"],
"evidence_refs": ["urn:srcos:evidence:doctor-report-20260718"],
"reversible": true,
"rollback_refs": ["urn:srcos:rollback:fix-shell-rollback-20260718"],
"sensitive_data_excluded": true,
"downstream_consumers": ["agentplane", "sociosphere", "prophet-cli"]
}
11 changes: 11 additions & 0 deletions contracts/examples/doctor-report.INVALID-mutation-claimed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"_invalid": "doctor_report must not claim mutation_performed=true",
"id": "urn:srcos:workstation-action-boundary:bad-doctor-20260718",
"specVersion": "0.1.0",
"artifact_kind": "doctor_report",
"requested_action": "doctor workstation-v0",
"mutation_planned": false,
"mutation_performed": true,
"evidence_refs": ["urn:srcos:evidence:doctor-sh-output-20260718"],
"sensitive_data_excluded": true
}
11 changes: 11 additions & 0 deletions contracts/examples/doctor-report.valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "urn:srcos:workstation-action-boundary:doctor-report-workstation-v0-20260718",
"specVersion": "0.1.0",
"artifact_kind": "doctor_report",
"requested_action": "doctor workstation-v0",
"mutation_planned": false,
"mutation_performed": false,
"evidence_refs": ["urn:srcos:evidence:doctor-sh-output-20260718"],
"sensitive_data_excluded": true,
"downstream_consumers": ["agentplane", "sociosphere"]
}
15 changes: 15 additions & 0 deletions contracts/examples/fix-plan.INVALID-no-policy-refs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"_invalid": "applied_fix without policy_decision_refs — forbidden",
"id": "urn:srcos:workstation-action-boundary:bad-fix-no-policy-20260718",
"specVersion": "0.1.0",
"artifact_kind": "applied_fix",
"requested_action": "fix shell",
"mutation_planned": true,
"mutation_performed": true,
"performed_by": "some-agent",
"before_refs": ["urn:srcos:evidence:before"],
"after_refs": ["urn:srcos:evidence:after"],
"policy_decision_refs": [],
"evidence_refs": [],
"sensitive_data_excluded": true
}
99 changes: 99 additions & 0 deletions contracts/validate-workstation-boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Validate WorkstationActionBoundary records against the lifecycle-boundary contract.

Usage:
python3 contracts/validate-workstation-boundary.py <record.json> [<record.json> ...]

Exits 0 if all records pass. Exits 1 if any fail.
"""

import json
import sys

MUTATION_FREE_KINDS = {"doctor_report", "fix_plan"}
MUTATION_REQUIRED_KINDS = {"applied_fix"}
POLICY_REQUIRED_KINDS = {"applied_fix", "admission_decision"}


def validate(path):
errors = []
try:
with open(path) as f:
r = json.load(f)
except Exception as e:
return [f"parse error: {e}"]

kind = r.get("artifact_kind", "")
mutation_performed = r.get("mutation_performed", False)
mutation_planned = r.get("mutation_planned", False)

# doctor/report/fix-plan must never claim mutation_performed
if kind in MUTATION_FREE_KINDS and mutation_performed:
errors.append(
f"FAIL [{kind}]: mutation_performed=true is forbidden for {kind}. "
"A doctor/report/fix-plan artifact must not imply a mutation was executed."
)

# applied_fix must claim mutation_performed
if kind in MUTATION_REQUIRED_KINDS and not mutation_performed:
errors.append(
f"FAIL [{kind}]: mutation_performed=false for applied_fix. "
"An applied_fix record must confirm the mutation occurred."
)

# mutation requires policy refs
if mutation_performed and not r.get("policy_decision_refs"):
errors.append(
"FAIL: mutation_performed=true but policy_decision_refs is empty. "
"Every mutation requires an explicit policy/admission decision ref."
)

# mutation requires before/after evidence
if mutation_performed and not r.get("before_refs"):
errors.append("FAIL: mutation_performed=true but before_refs is empty.")
if mutation_performed and not r.get("after_refs"):
errors.append("FAIL: mutation_performed=true but after_refs is empty.")

# mutation requires performed_by
if mutation_performed and not r.get("performed_by"):
errors.append("FAIL: mutation_performed=true but performed_by is missing.")

# fix_plan without policy refs is fine — it's a plan, not a decision
# but fix_plan claiming mutation_planned=false is suspicious
if kind == "fix_plan" and not mutation_planned:
errors.append(
"WARN [fix_plan]: mutation_planned=false on a fix_plan. "
"A fix plan that proposes no mutation is likely a doctor_report."
)

# sensitive data must be excluded
if r.get("sensitive_data_excluded") is not True:
errors.append(
"FAIL: sensitive_data_excluded must be true. "
"Raw credentials, prompt content, and private keys must not appear in boundary records."
)

return errors


def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <record.json> [...]")
sys.exit(2)

overall_pass = True
for path in sys.argv[1:]:
errors = validate(path)
if errors:
overall_pass = False
print(f"\n{path}: FAIL")
for e in errors:
print(f" {e}")
else:
print(f"{path}: OK")

sys.exit(0 if overall_pass else 1)


if __name__ == "__main__":
main()
100 changes: 100 additions & 0 deletions contracts/workstation-action-boundary.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:srcos:source-os:schema:workstation-action-boundary:v0.1.0",
"title": "WorkstationActionBoundary",
"description": "Records the boundary between observation, planning, policy, and mutation in SourceOS workstation flows. Prevents doctor/report artifacts from implying mutations were performed, and prevents fix plans from implying policy admission.",
"type": "object",
"additionalProperties": false,
"required": [
"id",
"specVersion",
"artifact_kind",
"requested_action",
"mutation_planned",
"mutation_performed",
"evidence_refs"
],
"properties": {
"id": {
"type": "string",
"description": "URN of this boundary record.",
"pattern": "^urn:srcos:workstation-action-boundary:.+"
},
"specVersion": {
"type": "string",
"const": "0.1.0"
},
"artifact_kind": {
"type": "string",
"description": "Which stage of the workstation flow produced this record.",
"enum": [
"doctor_report",
"fix_plan",
"admission_decision",
"applied_fix",
"post_fix_report"
]
},
"evidence_refs": {
"type": "array",
"description": "URNs of evidence artifacts this record is based on (observations, probes, receipts).",
"items": { "type": "string" }
},
"policy_decision_refs": {
"type": "array",
"description": "URNs of policy/admission decisions that authorized any mutation. Required when mutation_performed is true.",
"items": { "type": "string" }
},
"requested_action": {
"type": "string",
"description": "The action that was requested (e.g., fix-shell, apply-profile, re-index)."
},
"mutation_planned": {
"type": "boolean",
"description": "Whether a mutation was proposed in this artifact (true for fix_plan, admission_decision, applied_fix)."
},
"mutation_performed": {
"type": "boolean",
"description": "Whether a mutation was actually executed. Must be false for doctor_report and fix_plan."
},
"performed_by": {
"type": "string",
"description": "Identity of the actor that performed the mutation. Required when mutation_performed is true."
},
"before_refs": {
"type": "array",
"description": "URNs of evidence capturing system state before mutation. Required when mutation_performed is true.",
"items": { "type": "string" }
},
"after_refs": {
"type": "array",
"description": "URNs of evidence capturing system state after mutation. Required when mutation_performed is true.",
"items": { "type": "string" }
},
"reversible": {
"type": "boolean",
"description": "Whether this mutation can be rolled back."
},
"rollback_refs": {
"type": "array",
"description": "URNs of rollback artifacts or procedures if reversible is true.",
"items": { "type": "string" }
},
"downstream_consumers": {
"type": "array",
"description": "Repos/services that consume this artifact (e.g., agentplane, sociosphere, prophet-cli). These are consumers, not implicit authorities.",
"items": { "type": "string" }
},
"sensitive_data_excluded": {
"type": "boolean",
"description": "Confirms that raw sensitive local data (credentials, raw prompt content, private keys) is not included in this record.",
"const": true
}
},
"if": {
"properties": { "mutation_performed": { "const": true } }
},
"then": {
"required": ["policy_decision_refs", "performed_by", "before_refs", "after_refs"]
}
}
65 changes: 65 additions & 0 deletions workstation/bin/sourceos
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Usage:
sourceos profile list
sourceos profile apply <profile>
sourceos doctor [<profile>]
sourceos index status [--json]
sourceos index explain --file <path>

Notes:
- This CLI is the implementation surface. Design/RFCs live in SociOS-Linux/enhancements.
Expand Down Expand Up @@ -50,6 +52,50 @@ doctor() {
exec "$doc"
}

index_status() {
local json_mode=0
[[ "${1:-}" == "--json" ]] && json_mode=1

local queue_depth=0
local generation="stub-v0"
local failed_extractors=0
local paused_lanes=0
local policy_denied_count=0
local status="stub"
local note="sourceos-indexd is not yet running. This is the stub surface (issue #162)."

if [[ "$json_mode" -eq 1 ]]; then
printf '{"status":"%s","generation":"%s","queue_depth":%d,"failed_extractors":%d,"paused_lanes":%d,"policy_denied_count":%d,"note":"%s"}\n' \
"$status" "$generation" "$queue_depth" "$failed_extractors" "$paused_lanes" "$policy_denied_count" "$note"
else
printf "SourceOS Index Status\n"
printf " status: %s\n" "$status"
printf " generation: %s\n" "$generation"
printf " queue_depth: %d\n" "$queue_depth"
printf " failed_extractors: %d\n" "$failed_extractors"
printf " paused_lanes: %d\n" "$paused_lanes"
printf " policy_denied_count: %d\n" "$policy_denied_count"
printf " note: %s\n" "$note"
fi
}

index_explain() {
local file=""
while [[ $# -gt 0 ]]; do
case "$1" in
--file) shift; file="${1:-}"; shift ;;
*) err "unknown option: $1"; exit 2 ;;
esac
done
[[ -n "$file" ]] || { err "--file <path> required"; exit 2; }

local abs_path
abs_path="$(realpath "$file" 2>/dev/null || echo "$file")"

printf '{"file":"%s","last_observed_event":"never","path_policy":"unknown","extractor_identity":"stub","receipt_status":"absent","generation_membership":"none","semantic_indexing_enabled":false,"cloud_exposure_enabled":false,"note":"sourceos-indexd stub (issue #162). Full explain requires indexd daemon."}\n' \
"$abs_path"
}

main() {
local cmd=${1:-}
case "$cmd" in
Expand Down Expand Up @@ -78,6 +124,25 @@ main() {
shift
doctor "${1:-workstation-v0}"
;;
index)
shift
local sub=${1:-}
case "$sub" in
status)
shift
index_status "${@}"
;;
explain)
shift
index_explain "${@}"
;;
*)
err "unknown index subcommand: $sub"
usage
exit 2
;;
esac
;;
*)
err "unknown command: $cmd"
usage
Expand Down