aero-agent-roles 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/manifest.json +1 -1
- package/package.json +3 -2
- package/roles/do254-hardware-engineer/ROLE.md +4 -4
- package/roles/do254-hardware-engineer/cli.py +2 -2
- package/roles/do254-hardware-engineer/core/do254_hardware_core.py +4 -4
- package/roles/do254-hardware-engineer/tests/test_do254_bundle_profile.py +2 -2
- package/roles/do254-hardware-engineer/tests/test_role_do254_hardware.py +2 -2
- package/roles/space-systems-engineer/ROLE.md +2 -2
- package/roles/space-systems-engineer/tests/test_role_space_systems_engineer.py +1 -1
- package/roles/state-estimation-engineer/cli.py +2 -0
- package/roles/systems-integration-engineer/ROLE.md +2 -2
- package/roles/systems-integration-engineer/tests/test_role_systems_integration_engineer.py +1 -1
- package/scripts/evidence.py +198 -0
- package/scripts/prepack.js +46 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**The role layer for aerospace engineering agents** — professional roles that bind verified [Aero Agent Skills](https://github.com/ashfordeOU/aero-agent-skills) into end-to-end deliverables, with evidence gates and a human sign-off stop, shipped as an npm CLI and an MCP server. Built and maintained by [Ashforde OÜ](https://ashforde.org).
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> Published on npm as [`aero-agent-roles`](https://www.npmjs.com/package/aero-agent-roles). Install with `npm i -g aero-agent-roles`, then run `aero-roles --help`.
|
|
6
6
|
|
|
7
7
|
Full role bank, figures, and provenance: [github.com/ashfordeOU/aero-agent-roles](https://github.com/ashfordeOU/aero-agent-roles) · [ashforde.org/aeroagentroles](https://ashforde.org/aeroagentroles)
|
|
8
8
|
|
package/manifest.json
CHANGED
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
],
|
|
196
196
|
"skills_bound": 4,
|
|
197
197
|
"tests": 38,
|
|
198
|
-
"stage_preview": "do254/hardware-planning -> do254/requirements-capture -> do254/verification -> do254/configuration-
|
|
198
|
+
"stage_preview": "do254/hardware-planning -> do254/requirements-capture -> do254/hardware-verification -> do254/hardware-configuration-manag -> core engine"
|
|
199
199
|
},
|
|
200
200
|
{
|
|
201
201
|
"slug": "engineering-analysis-engineer",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aero-agent-roles",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "The role layer for aerospace engineering agents: professional roles that bind verified Aero Agent Skills into end-to-end deliverables, with evidence gates and a human sign-off. A CLI and an MCP server. By Ashforde OU.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Ashforde OU <contact@ashforde.org> (https://ashforde.org)",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"bin/",
|
|
18
18
|
"lib/",
|
|
19
|
+
"scripts/",
|
|
19
20
|
"manifest.json",
|
|
20
21
|
"roles/",
|
|
21
22
|
"NOTICE"
|
|
@@ -43,4 +44,4 @@
|
|
|
43
44
|
"systems-engineering",
|
|
44
45
|
"skills-library"
|
|
45
46
|
]
|
|
46
|
-
}
|
|
47
|
+
}
|
|
@@ -14,8 +14,8 @@ standards_bound:
|
|
|
14
14
|
skills_bound:
|
|
15
15
|
- avionics/do254/hardware-planning
|
|
16
16
|
- avionics/do254/requirements-capture
|
|
17
|
-
- avionics/do254/verification
|
|
18
|
-
- avionics/do254/configuration-management
|
|
17
|
+
- avionics/do254/hardware-verification
|
|
18
|
+
- avionics/do254/hardware-configuration-management
|
|
19
19
|
tools_allowed: [stdlib, offline-file-processing]
|
|
20
20
|
forbidden:
|
|
21
21
|
- "issue certification approval"
|
|
@@ -65,8 +65,8 @@ The role produces:
|
|
|
65
65
|
|---|---|---|
|
|
66
66
|
| 1. Hardware planning | do254/hardware-planning | AEH class (simple/complex), PHAC scope, artifact set |
|
|
67
67
|
| 2. Requirements capture | do254/requirements-capture | requirement issue list, derived-vs-allocated classification, readiness verdict |
|
|
68
|
-
| 3. Verification strategy | do254/verification | verification methods, coverage ratio (0.98 A/B, 0.95 C/D), independence |
|
|
69
|
-
| 4. Configuration management | do254/configuration-management | change class, ECR/ECO path, HCI entries, baseline control |
|
|
68
|
+
| 3. Verification strategy | do254/hardware-verification | verification methods, coverage ratio (0.98 A/B, 0.95 C/D), independence |
|
|
69
|
+
| 4. Configuration management | do254/hardware-configuration-management | change class, ECR/ECO path, HCI entries, baseline control |
|
|
70
70
|
| 5. Process assurance | core engine | process assurance records plan |
|
|
71
71
|
| 6. Certification liaison | core engine | PHAC input, open items for the cert authority |
|
|
72
72
|
|
|
@@ -31,8 +31,8 @@ HAS_SKILLS = os.path.isdir(os.path.join(AEROSKILLS, "skills"))
|
|
|
31
31
|
# Bound avionics/do254 leaves and their logic files.
|
|
32
32
|
DO254_LEAVES = {
|
|
33
33
|
"hardware-planning": "avionics/do254/hardware-planning",
|
|
34
|
-
"verification": "avionics/do254/verification",
|
|
35
|
-
"configuration-management": "avionics/do254/configuration-management",
|
|
34
|
+
"verification": "avionics/do254/hardware-verification",
|
|
35
|
+
"configuration-management": "avionics/do254/hardware-configuration-management",
|
|
36
36
|
"requirements-capture": "avionics/do254/requirements-capture",
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -47,13 +47,13 @@ DALS = ("A", "B", "C", "D", "E")
|
|
|
47
47
|
|
|
48
48
|
# DO-254 verification logic: requirements-based test coverage ratio by
|
|
49
49
|
# level: 0.98 at levels A/B, 0.95 at levels C/D (as encoded in the bound
|
|
50
|
-
# avionics/do254/verification leaf). Level E (no safety effect) is
|
|
50
|
+
# avionics/do254/hardware-verification leaf). Level E (no safety effect) is
|
|
51
51
|
# typically outside DO-254 scope: no coverage target.
|
|
52
52
|
COVERAGE_RATIO = {"A": 0.98, "B": 0.98, "C": 0.95, "D": 0.95, "E": None}
|
|
53
53
|
|
|
54
54
|
# Verification independence by DAL: independent verification expected at
|
|
55
55
|
# the higher hardware design assurance levels (A/B) per the bound
|
|
56
|
-
# avionics/do254/verification leaf.
|
|
56
|
+
# avionics/do254/hardware-verification leaf.
|
|
57
57
|
INDEPENDENT = {"A": True, "B": True, "C": False, "D": False, "E": False}
|
|
58
58
|
|
|
59
59
|
# Life cycle data set by DAL: the hardware life cycle data items a PHAC
|
|
@@ -218,7 +218,7 @@ def planning_artifacts(classification: str) -> list:
|
|
|
218
218
|
|
|
219
219
|
|
|
220
220
|
# ---------------------------------------------------------------------------
|
|
221
|
-
# Verification rules (grounded in avionics/do254/verification leaf)
|
|
221
|
+
# Verification rules (grounded in avionics/do254/hardware-verification leaf)
|
|
222
222
|
# ---------------------------------------------------------------------------
|
|
223
223
|
|
|
224
224
|
def _check_dal(dal: str) -> None:
|
|
@@ -284,7 +284,7 @@ def structural_methods_text(dal: str) -> str:
|
|
|
284
284
|
|
|
285
285
|
|
|
286
286
|
# ---------------------------------------------------------------------------
|
|
287
|
-
# Configuration management (grounded in avionics/do254/configuration-management)
|
|
287
|
+
# Configuration management (grounded in avionics/do254/hardware-configuration-management)
|
|
288
288
|
# ---------------------------------------------------------------------------
|
|
289
289
|
|
|
290
290
|
VALID_HARDWARE_CLASSES = ("simple", "complex")
|
|
@@ -91,8 +91,8 @@ class TestDo254BundleProfile(unittest.TestCase):
|
|
|
91
91
|
# all four bound leaves dispatch at least one logic cross-check
|
|
92
92
|
leaves = {row["leaf"] for row in p["skills"]}
|
|
93
93
|
for leaf in ("avionics/do254/hardware-planning",
|
|
94
|
-
"avionics/do254/verification",
|
|
95
|
-
"avionics/do254/configuration-management",
|
|
94
|
+
"avionics/do254/hardware-verification",
|
|
95
|
+
"avionics/do254/hardware-configuration-management",
|
|
96
96
|
"avionics/do254/requirements-capture"):
|
|
97
97
|
self.assertIn(leaf, leaves, f"no dispatch row for {leaf}")
|
|
98
98
|
for row in p["skills"]:
|
|
@@ -26,8 +26,8 @@ PHAC_TEMPLATE = os.path.join(ROLE_DIR, "templates", "phac-template.md")
|
|
|
26
26
|
EXPECTED_BOUND = [
|
|
27
27
|
"avionics/do254/hardware-planning",
|
|
28
28
|
"avionics/do254/requirements-capture",
|
|
29
|
-
"avionics/do254/verification",
|
|
30
|
-
"avionics/do254/configuration-management",
|
|
29
|
+
"avionics/do254/hardware-verification",
|
|
30
|
+
"avionics/do254/hardware-configuration-management",
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
EXPECTED_STAGES = [
|
|
@@ -52,7 +52,7 @@ skills_bound:
|
|
|
52
52
|
- space-systems/subsystems/thermal-design
|
|
53
53
|
- space-systems/subsystems/propellant-tank-sizing
|
|
54
54
|
- space-systems/subsystems/command-data-handling
|
|
55
|
-
- space-systems/ecss/systems-engineering
|
|
55
|
+
- space-systems/ecss/e10-systems-engineering
|
|
56
56
|
- space-systems/ecss/software-engineering
|
|
57
57
|
- space-systems/ecss/software-verification
|
|
58
58
|
tools_allowed: [stdlib, offline-file-processing]
|
|
@@ -104,7 +104,7 @@ budgets. Do NOT use for aircraft certification or airframe roles.
|
|
|
104
104
|
| 9. Power/thermal | power-thermal-budget + solar-array-sizing + spacecraft-battery-sizing + thermal-design | power/thermal budgets |
|
|
105
105
|
| 10. Comms | communication-link-budget + antenna-aperture-sizing | link budget |
|
|
106
106
|
| 11. Propulsion/C&DH | propellant-tank-sizing + command-data-handling | propulsion sizing, C&DH |
|
|
107
|
-
| 12. ECSS context | ecss/systems-engineering + ecss/software-engineering + ecss/software-verification | process + software context |
|
|
107
|
+
| 12. ECSS context | ecss/e10-systems-engineering + ecss/software-engineering + ecss/software-verification | process + software context |
|
|
108
108
|
| 13. Report | (all above) | mission + subsystem reports |
|
|
109
109
|
|
|
110
110
|
## Evidence gates
|
|
@@ -54,7 +54,7 @@ EXPECTED_BOUND = [
|
|
|
54
54
|
"space-systems/subsystems/thermal-design",
|
|
55
55
|
"space-systems/subsystems/propellant-tank-sizing",
|
|
56
56
|
"space-systems/subsystems/command-data-handling",
|
|
57
|
-
"space-systems/ecss/systems-engineering",
|
|
57
|
+
"space-systems/ecss/e10-systems-engineering",
|
|
58
58
|
"space-systems/ecss/software-engineering",
|
|
59
59
|
"space-systems/ecss/software-verification"
|
|
60
60
|
]
|
|
@@ -20,6 +20,8 @@ estimation-filtering leaf logic files and cross-checks identical
|
|
|
20
20
|
computations (alpha-beta gains, Kalman recursion, EKF Jacobian, NEES,
|
|
21
21
|
effective sample size); agreement is recorded in provenance.json.
|
|
22
22
|
"""
|
|
23
|
+
from __future__ import annotations # PEP 604 unions (dict | None) on Python 3.9
|
|
24
|
+
|
|
23
25
|
import argparse
|
|
24
26
|
import importlib.util
|
|
25
27
|
import json
|
|
@@ -20,7 +20,7 @@ skills_bound:
|
|
|
20
20
|
- systems-engineering-safety/arp4754a/derived-requirements
|
|
21
21
|
- systems-engineering-safety/arp4754a/validation
|
|
22
22
|
- systems-engineering-safety/arp4754a/verification-planning
|
|
23
|
-
- systems-engineering-safety/arp4754a/configuration-management
|
|
23
|
+
- systems-engineering-safety/arp4754a/systems-configuration-management
|
|
24
24
|
tools_allowed: [stdlib, offline-file-processing]
|
|
25
25
|
forbidden:
|
|
26
26
|
- "issue certification approval"
|
|
@@ -85,7 +85,7 @@ The role produces:
|
|
|
85
85
|
| 5. Derived requirements | arp4754a/derived-requirements | derived requirement register with source/rationale obligation |
|
|
86
86
|
| 6. Validation | arp4754a/validation | validation closure score, independence need at A/B |
|
|
87
87
|
| 7. Integration verification | arp4754a/verification-planning | acceptable methods per level, method-to-requirement coverage |
|
|
88
|
-
| 8. Configuration management | arp4754a/configuration-management | CI baseline, change classification (major/minor), change log |
|
|
88
|
+
| 8. Configuration management | arp4754a/systems-configuration-management | CI baseline, change classification (major/minor), change log |
|
|
89
89
|
| 9. Plan + gates | (all above) | the System Development Assurance and Integration Plan + bundle |
|
|
90
90
|
|
|
91
91
|
## Evidence gates
|
|
@@ -40,7 +40,7 @@ EXPECTED_BOUND = [
|
|
|
40
40
|
"systems-engineering-safety/arp4754a/derived-requirements",
|
|
41
41
|
"systems-engineering-safety/arp4754a/validation",
|
|
42
42
|
"systems-engineering-safety/arp4754a/verification-planning",
|
|
43
|
-
"systems-engineering-safety/arp4754a/configuration-management",
|
|
43
|
+
"systems-engineering-safety/arp4754a/systems-configuration-management",
|
|
44
44
|
]
|
|
45
45
|
|
|
46
46
|
EXPECTED_STAGES = [
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""evidence.py - shared evidence-bundle writer for role CLIs.
|
|
3
|
+
|
|
4
|
+
Implements docs/PROTOCOL.md v1: every role emits
|
|
5
|
+
deliverable.md + evidence/{model.json, gates.json, provenance.json}
|
|
6
|
+
so any harness can consume results programmatically.
|
|
7
|
+
|
|
8
|
+
Usage (from a role cli.py):
|
|
9
|
+
import sys, os
|
|
10
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
11
|
+
"..", "..", "scripts"))
|
|
12
|
+
import evidence
|
|
13
|
+
evidence.write_bundle(out_dir, role_slug, model, gates, provenance)
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _utcnow() -> str:
|
|
23
|
+
return datetime.now(timezone.utc).isoformat(timespec="seconds")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_profile(path: str) -> dict | None:
|
|
27
|
+
"""Load and validate a program profile JSON (docs/PROFILE-SCHEMA.md)."""
|
|
28
|
+
if not path or not os.path.exists(path):
|
|
29
|
+
return None
|
|
30
|
+
with open(path) as f:
|
|
31
|
+
p = json.load(f)
|
|
32
|
+
if not isinstance(p, dict) or not p.get("customer"):
|
|
33
|
+
raise ValueError("profile must be a JSON object with 'customer'")
|
|
34
|
+
return p
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def profile_header_block(profile: dict) -> str:
|
|
38
|
+
"""Render a markdown header block for a program profile.
|
|
39
|
+
|
|
40
|
+
Returns "" when profile is None. NEVER an approval — always a DRAFT
|
|
41
|
+
context header for human review.
|
|
42
|
+
"""
|
|
43
|
+
if not profile:
|
|
44
|
+
return ""
|
|
45
|
+
lines = [
|
|
46
|
+
"## Program context (profile)",
|
|
47
|
+
"",
|
|
48
|
+
f"- Customer: {profile.get('customer', '')}",
|
|
49
|
+
f"- Program: {profile.get('program', '')}",
|
|
50
|
+
f"- Basis: {profile.get('basis', '')} · "
|
|
51
|
+
f"Authority: {profile.get('authority', '')}",
|
|
52
|
+
]
|
|
53
|
+
if profile.get("der"):
|
|
54
|
+
lines.append(f"- DER/CVE: {profile['der']}")
|
|
55
|
+
if profile.get("document_prefix"):
|
|
56
|
+
rev = profile.get("revision", "")
|
|
57
|
+
lines.append(f"- Document: {profile['document_prefix']}"
|
|
58
|
+
+ (f" {rev}" if rev else ""))
|
|
59
|
+
if profile.get("standards"):
|
|
60
|
+
lines.append("- Standards: " + ", ".join(profile["standards"]))
|
|
61
|
+
if profile.get("notes"):
|
|
62
|
+
lines.append(f"- Notes: {profile['notes']}")
|
|
63
|
+
lines += [
|
|
64
|
+
"",
|
|
65
|
+
"> This document is a DRAFT for human review within the program "
|
|
66
|
+
"sign-off chain. It is not an approval and carries no regulatory "
|
|
67
|
+
"authority.",
|
|
68
|
+
"",
|
|
69
|
+
]
|
|
70
|
+
return "\n".join(lines)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def dispatch_crosscheck(leaf: str, skill_fn: str, core_fn,
|
|
74
|
+
tolerance: float = 1e-6,
|
|
75
|
+
skills_root: str = "") -> dict | None:
|
|
76
|
+
"""Run a bound AeroSkills leaf's logic fn and compare to the core fn.
|
|
77
|
+
|
|
78
|
+
Both functions are called with the same kwargs. Returns a provenance
|
|
79
|
+
row: {leaf, logic_file, function, dispatched, core_value,
|
|
80
|
+
skill_value, delta, agrees}. Returns None if the skill logic file or
|
|
81
|
+
function is unavailable.
|
|
82
|
+
|
|
83
|
+
leaf e.g. 'structures/loads/gust-maneuver-loads'
|
|
84
|
+
skill_fn the function name inside the leaf's *logic.py
|
|
85
|
+
core_fn a zero-arg callable returning the core's computed value
|
|
86
|
+
skills_root AeroSkills root (default: AEROSKILLS_DEV or ~/company-ops/aero-agent-skills)
|
|
87
|
+
"""
|
|
88
|
+
import importlib.util # local import (stdlib)
|
|
89
|
+
root = skills_root or os.environ.get(
|
|
90
|
+
"AEROSKILLS_DEV", os.path.expanduser("~/company-ops/aero-agent-skills"))
|
|
91
|
+
logic_dir = os.path.join(root, "skills", leaf, "scripts")
|
|
92
|
+
if not os.path.isdir(logic_dir):
|
|
93
|
+
return None
|
|
94
|
+
logic_files = sorted(f for f in os.listdir(logic_dir)
|
|
95
|
+
if f.endswith("_logic.py"))
|
|
96
|
+
if not logic_files:
|
|
97
|
+
return None
|
|
98
|
+
# try each logic file until one exposes the requested fn
|
|
99
|
+
for lf in logic_files:
|
|
100
|
+
path = os.path.join(logic_dir, lf)
|
|
101
|
+
spec = importlib.util.spec_from_file_location("role_dispatch", path)
|
|
102
|
+
mod = importlib.util.module_from_spec(spec)
|
|
103
|
+
try:
|
|
104
|
+
spec.loader.exec_module(mod)
|
|
105
|
+
except Exception:
|
|
106
|
+
continue
|
|
107
|
+
if not hasattr(mod, skill_fn):
|
|
108
|
+
continue
|
|
109
|
+
try:
|
|
110
|
+
skill_value = getattr(mod, skill_fn)()
|
|
111
|
+
core_value = core_fn()
|
|
112
|
+
if not isinstance(skill_value, (int, float)) or \
|
|
113
|
+
not isinstance(core_value, (int, float)):
|
|
114
|
+
continue
|
|
115
|
+
delta = abs(float(core_value) - float(skill_value))
|
|
116
|
+
return {
|
|
117
|
+
"leaf": leaf,
|
|
118
|
+
"logic_file": lf,
|
|
119
|
+
"function": skill_fn,
|
|
120
|
+
"dispatched": True,
|
|
121
|
+
"core_value": round(float(core_value), 6),
|
|
122
|
+
"skill_value": round(float(skill_value), 6),
|
|
123
|
+
"delta": round(delta, 9),
|
|
124
|
+
"agrees": delta <= tolerance,
|
|
125
|
+
"tolerance": tolerance,
|
|
126
|
+
}
|
|
127
|
+
except Exception:
|
|
128
|
+
continue
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def write_json(path: str, obj: dict) -> None:
|
|
133
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
134
|
+
with open(path, "w") as f:
|
|
135
|
+
json.dump(obj, f, indent=2, sort_keys=True)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def write_bundle(out_md: str, role: str, deliverable_type: str,
|
|
139
|
+
model: dict, gates: dict, provenance: dict) -> dict:
|
|
140
|
+
"""Write the evidence bundle next to the markdown deliverable.
|
|
141
|
+
|
|
142
|
+
out_md path of the deliverable .md (evidence/ goes beside it)
|
|
143
|
+
role role slug, e.g. "structures-loads-engineer"
|
|
144
|
+
model the computed content model (build_<deliverable>() result)
|
|
145
|
+
gates gates dict from check_<deliverable>() (has 'all_pass')
|
|
146
|
+
provenance provenance dict (core + skills + disclaimer)
|
|
147
|
+
|
|
148
|
+
Returns the paths written: {"model": ..., "gates": ..., "provenance": ...}
|
|
149
|
+
"""
|
|
150
|
+
ev_dir = os.path.join(os.path.dirname(os.path.abspath(out_md)), "evidence")
|
|
151
|
+
os.makedirs(ev_dir, exist_ok=True)
|
|
152
|
+
|
|
153
|
+
# Normalize gates into the protocol shape
|
|
154
|
+
gate_rows = []
|
|
155
|
+
for k, v in gates.items():
|
|
156
|
+
if k in ("all_pass", "exit_code", "checker"):
|
|
157
|
+
continue
|
|
158
|
+
if isinstance(v, dict):
|
|
159
|
+
gate_rows.append({"gate": k, "pass": bool(v.get("pass", v.get("ok", False))),
|
|
160
|
+
"detail": str(v.get("detail", ""))})
|
|
161
|
+
else:
|
|
162
|
+
gate_rows.append({"gate": k, "pass": bool(v)})
|
|
163
|
+
gates_payload = {
|
|
164
|
+
"schema_version": 1,
|
|
165
|
+
"role": role,
|
|
166
|
+
"checker": gates.get("checker", "check_<deliverable>"),
|
|
167
|
+
"gates": gate_rows,
|
|
168
|
+
"all_pass": bool(gates.get("all_pass", all(r["pass"] for r in gate_rows))),
|
|
169
|
+
"exit_code": 0 if gates.get("all_pass", all(r["pass"] for r in gate_rows)) else 1,
|
|
170
|
+
"checked_at": _utcnow(),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
model_payload = dict(model or {})
|
|
174
|
+
model_payload.setdefault("schema_version", 1)
|
|
175
|
+
model_payload.setdefault("role", role)
|
|
176
|
+
model_payload.setdefault("deliverable_type", deliverable_type)
|
|
177
|
+
model_payload.setdefault("generated", _utcnow())
|
|
178
|
+
model_payload.setdefault("status", "draft-for-review")
|
|
179
|
+
# generator = the producing agent/role. The VERIFIER (gates.checker) must be
|
|
180
|
+
# independent of it (founder 2026-09-10): no bot grades its own output.
|
|
181
|
+
# The roles gates are deterministic Python checks, so they qualify; this
|
|
182
|
+
# field makes the relationship checkable by scripts/verify-independence.py.
|
|
183
|
+
model_payload.setdefault("generator", role)
|
|
184
|
+
|
|
185
|
+
prov_payload = dict(provenance or {})
|
|
186
|
+
prov_payload.setdefault("schema_version", 1)
|
|
187
|
+
prov_payload.setdefault("role", role)
|
|
188
|
+
prov_payload.setdefault("disclaimer",
|
|
189
|
+
"DRAFT for human review. Not an approval document.")
|
|
190
|
+
|
|
191
|
+
model_path = os.path.join(ev_dir, "model.json")
|
|
192
|
+
gates_path = os.path.join(ev_dir, "gates.json")
|
|
193
|
+
prov_path = os.path.join(ev_dir, "provenance.json")
|
|
194
|
+
write_json(model_path, model_payload)
|
|
195
|
+
write_json(gates_path, gates_payload)
|
|
196
|
+
write_json(prov_path, prov_payload)
|
|
197
|
+
|
|
198
|
+
return {"model": model_path, "gates": gates_path, "provenance": prov_path}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Runs on `npm pack` / `npm publish` from the repo checkout: copies the
|
|
2
|
+
// payload (roles tree, NOTICE) from the repo root into the package so the
|
|
3
|
+
// tarball is self-contained. The copy is gitignored — the repo tree stays
|
|
4
|
+
// the single source of truth.
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
|
|
10
|
+
const pkg = path.join(__dirname, '..');
|
|
11
|
+
const repo = path.join(pkg, '..', '..');
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(path.join(repo, 'roles')) || !fs.existsSync(path.join(repo, 'manifest.json'))) {
|
|
14
|
+
console.error('prepack: must run from the aero-agent-roles repo checkout (roles/ not found two levels up)');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fs.rmSync(path.join(pkg, 'roles'), { recursive: true, force: true });
|
|
19
|
+
fs.cpSync(path.join(repo, 'roles'), path.join(pkg, 'roles'), { recursive: true });
|
|
20
|
+
fs.copyFileSync(path.join(repo, 'NOTICE'), path.join(pkg, 'NOTICE'));
|
|
21
|
+
|
|
22
|
+
// scripts/evidence.py travels with the tarball. Every role's cli.py does
|
|
23
|
+
// `sys.path.insert(.., '../../scripts'); import evidence`, so without this the
|
|
24
|
+
// installed package has 38 CLIs that all die on import. Shipped 1.2.0 did.
|
|
25
|
+
fs.mkdirSync(path.join(pkg, 'scripts'), { recursive: true });
|
|
26
|
+
fs.copyFileSync(path.join(repo, 'scripts', 'evidence.py'),
|
|
27
|
+
path.join(pkg, 'scripts', 'evidence.py'));
|
|
28
|
+
|
|
29
|
+
// Strip Python bytecode caches: local test runs leave __pycache__/*.pyc
|
|
30
|
+
// behind, and a plain cpSync copies them straight into the tarball. Never
|
|
31
|
+
// ship compiled bytecode in a public package regardless of dev-tree state.
|
|
32
|
+
function stripPycache(dir) {
|
|
33
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
34
|
+
const full = path.join(dir, entry.name);
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
if (entry.name === '__pycache__') {
|
|
37
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
38
|
+
} else {
|
|
39
|
+
stripPycache(full);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
stripPycache(path.join(pkg, 'roles'));
|
|
45
|
+
|
|
46
|
+
console.log('prepack: bundled roles/, scripts/evidence.py, NOTICE from repo root (pycache stripped)');
|