叙事层重构:README 重写(CaaS 三层叙事 + Today/Next/Long-term + 部署架构 + Is/Is-not +… #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: conformance | |
| on: | |
| push: | |
| paths: | |
| - 'standard/**' | |
| - 'reference/**' | |
| - 'conformance/**' | |
| - 'schemas/**' | |
| - 'registry/**' | |
| - 'verify_vectors.py' | |
| pull_request: | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Core integrity tests | |
| env: | |
| PYTHONPATH: reference | |
| run: | | |
| python3 - <<'PY' | |
| import sys, json, tempfile, pathlib, os, subprocess | |
| sys.path.insert(0, ".") | |
| from collector.correlator.correlator import connect, store_observation, match_invocations | |
| from collector.aggregator.aggregator import compute | |
| from collector.usage import empty_observation, new_observation_id | |
| # 50% bug 修复:100 次双边调用 -> share=1.0 | |
| tmp = pathlib.Path(tempfile.mkdtemp()) | |
| conn = connect(tmp / "collector.db") | |
| for n in range(100): | |
| for side, principal in (("client", "codex-hook@t"), ("server", "mcp-wrapper@t")): | |
| o = empty_observation(); o.update(dict( | |
| observation_id=new_observation_id(), | |
| observed_at=f"2026-08-16T00:{n//60:02d}:{n%60:02d}Z", | |
| observer_principal=principal, observer_side=side, | |
| provenance="hook" if side=="client" else "wrapper", | |
| project_id="github.com/foo/bar", tool="foo.search", | |
| tool_call_id=f"tc-{n}", session_key=f"sess-{n%10}" if side=="client" else None, | |
| outcome="success", lifecycle_stage="L2")) | |
| store_observation(conn, o) | |
| match_invocations(conn) | |
| s = compute(conn, "github.com/foo/bar") | |
| assert s["logical_invocations"] == 100, s | |
| assert s["corroborated_share"] == 1.0, s | |
| print("core: 100 bilateral -> share 1.0 OK") | |
| # 单侧 -> E0 | |
| o = empty_observation(); o.update(dict( | |
| observation_id=new_observation_id(), observed_at="2026-08-16T03:00:00Z", | |
| observer_principal="codex-hook@t", observer_side="client", provenance="hook", | |
| project_id="github.com/foo/bar", tool="foo.only", tool_call_id="tc-s", | |
| session_key="sess-x", outcome="unknown", lifecycle_stage="L1")) | |
| store_observation(conn, o) | |
| match_invocations(conn) | |
| ev = conn.execute("SELECT evidence FROM invocations WHERE tool='foo.only'").fetchone() | |
| assert ev["evidence"] == "E0", ev["evidence"] | |
| print("core: single observation -> E0 OK") | |
| PY | |
| - name: AgentMeasure test vectors | |
| env: | |
| PYTHONPATH: reference | |
| run: | | |
| python3 verify_vectors.py | |
| - name: Metric contract vectors | |
| env: | |
| PYTHONPATH: reference | |
| run: | | |
| python3 conformance/runners/run_metrics.py | |
| - name: Choice end-to-end (M2.2 / M2.5) | |
| env: | |
| PYTHONPATH: reference | |
| run: | | |
| python3 - <<'PY' | |
| import sys, json, tempfile, pathlib | |
| sys.path.insert(0, ".") | |
| from collector.choice import connect, ingest_choice_events, selection_metrics, conditional_choice_share | |
| tmp = pathlib.Path(tempfile.mkdtemp()) | |
| conn = connect(tmp / "choice.db") | |
| events = [ | |
| {"type": "presented", "decision_id": f"d{i}", "candidate_set_id": "c1", | |
| "project_id": "github.com/foo/bar", "tool": "Exa", "choice_mode": "exclusive", | |
| "context": "production", "validity": "normal", "ts": "2026-08-16T00:00:00Z"} | |
| for i in range(10) | |
| ] + [ | |
| {"type": "selected", "decision_id": f"d{i}", "candidate_set_id": "c1", | |
| "project_id": "github.com/foo/bar", "tool": "Exa", "choice_mode": "exclusive", | |
| "decision_authority": "model", "selection_constraint": "autonomous", | |
| "context": "production", "validity": "normal", "ts": "2026-08-16T00:01:00Z"} | |
| for i in range(4) | |
| ] | |
| p = tmp / "events.jsonl" | |
| p.write_text("\n".join(json.dumps(e) for e in events)) | |
| ingest_choice_events(conn, p) | |
| sm = selection_metrics(conn, "github.com/foo/bar") | |
| assert sm["tools"][0]["observed_selection_rate"] == 0.4, sm | |
| cs = conditional_choice_share(conn, "Exa", "Tavily", project_id="github.com/foo/bar") | |
| assert cs["co_presented_decisions"] == 0, cs # Tavily 未同台 -> fail-closed 空分母 | |
| print("choice: M2.2=0.4, M2.5 fail-closed OK") | |
| PY | |
| - name: Registry validation | |
| run: | | |
| python3 registry/validate_entities.py | |
| - name: Adapter privacy tests | |
| run: | | |
| python3 - <<'PY' | |
| import sys, json, tempfile, pathlib, os, subprocess | |
| tmp = pathlib.Path(tempfile.mkdtemp()) | |
| env = dict(os.environ, AGENTMEASURE_EVENTS_DIR=str(tmp / "hook")) | |
| subprocess.run([sys.executable, "reference/adapters/codex/hook_agent.py"], input=json.dumps({ | |
| "hook_event_name": "PostToolUse", "tool_name": "Bash", "tool_use_id": "tu-1", | |
| "session_id": "RAW-SESSION-42", | |
| "tool_input": {"command": "cat /etc/shadow"}, | |
| "tool_response": "SUPER-SECRET", | |
| "trace_id": "ignored", "is_error": True}), capture_output=True, text=True, env=env) | |
| raw = (tmp / "hook" / "codex-hook-events.jsonl").read_text() | |
| assert "RAW-SESSION-42" not in raw and "SUPER-SECRET" not in raw and "shadow" not in raw | |
| assert "trace_id" not in raw and "is_error" not in raw | |
| assert "p-" in raw | |
| print("adapter privacy OK") | |
| PY |