From 5eeaf532e483d8a57fbf8ce1ee8f3398c21d3ab2 Mon Sep 17 00:00:00 2001 From: bitmaster162 Date: Fri, 16 Oct 2026 03:47:45 +0700 Subject: [PATCH 1/4] feat(company-twin): add fictional client walkthrough [skip ci] --- demo/company_twin_client_demo.html | 246 +++++++++++++++++++++++++ demo/company_twin_client_demo.py | 219 ++++++++++++++++++++++ docs/COMPANY_TWIN_CLIENT_DEMO_R1.md | 84 +++++++++ tests/test_company_twin_client_demo.py | 183 ++++++++++++++++++ 4 files changed, 732 insertions(+) create mode 100644 demo/company_twin_client_demo.html create mode 100644 demo/company_twin_client_demo.py create mode 100644 docs/COMPANY_TWIN_CLIENT_DEMO_R1.md create mode 100644 tests/test_company_twin_client_demo.py diff --git a/demo/company_twin_client_demo.html b/demo/company_twin_client_demo.html new file mode 100644 index 00000000..3ef0b383 --- /dev/null +++ b/demo/company_twin_client_demo.html @@ -0,0 +1,246 @@ + + + + + + +Northstar Labs — ContinuityOS Company Twin (Fictional Demo) + + + +
+
+
ContinuityOS / Company Twin
+
Synthetic demo · Fictional company · Read only
+
+
+
Northstar Labs · fictional B2B SaaS · 2025
+

A company's memory,
with decisions you can trace.

+

Replay what the organization knew at any date within 2025. See who decided, why, and which fictional records support it. Switch roles to reveal enforced access boundaries—not a chatbot with unrestricted access.

+
+
12fictional human staff
+
3teams
+
1human-supervised research agent
+
0live connectors or actions
+
+
+
+
+
+
+
+
+
+
Role selection is a local presentation simulation, not user authentication. All people, messages, tasks, and evidence below are invented.
+ +
+
Visible decisions—
+
Visible evidence—
+
Visible events—
+
As-of date—
+
+
+
+

Company timeline

Facts · decisions · outcomes
+
Loading fictional history…
+
+
+

Decision ledger

Who · what · why · evidence
+
Loading decisions…
+
+
+

Authority & access

P2C policy receipts
+
—
+
+

READ and PROPOSE are policy capabilities, not executable buttons. APPROVE/EXECUTE cannot be performed by the agent.

+
+
+

Organization & managed agent

Director → people/teams → memory → agent
+
+
Human-reviewed proposals
+
+
+
+ + +
+ + diff --git a/demo/company_twin_client_demo.py b/demo/company_twin_client_demo.py new file mode 100644 index 00000000..cecf8528 --- /dev/null +++ b/demo/company_twin_client_demo.py @@ -0,0 +1,219 @@ +"""Local, synthetic-only client showcase over the existing P2A/P2C/P2D stack. + +This is a presentation adapter: no new memory engine, policy evaluator, +external connector, model invocation, or execution authority. +Launch from a verified source checkout: + python -B -m demo.company_twin_client_demo --check + python -B -m demo.company_twin_client_demo +""" +from __future__ import annotations + +import argparse +from copy import deepcopy +from http import HTTPStatus +import json +from pathlib import Path +from typing import Any + +from continuityos.company_twin import load_dataset +from continuityos.company_twin_console import ( + _ConsoleHandler, build_snapshot, make_server, validate_bundle, +) +from continuityos.company_twin_policy import ACTIONS + +ROOT = Path(__file__).resolve().parent.parent +BASE_FIXTURE = ROOT / "examples" / "company_twin" / "northstar_labs_2025.json" +HTML_FILE = Path(__file__).with_suffix(".html") +DEMO_PORT = 8769 +DEMO_PERIOD = ("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z") +TENANT = "tenant:northstar-synthetic-demo" + +# Exactly twelve fictional humans (including one director), plus one managed agent. +# Every human belongs to a single organizational slot, not to a real directory. +PEOPLE = ( + ("alice", "Alice Chen", "DIRECTOR", None, "Director"), + ("bob", "Bob Reyes", "WORKER", "sales", "Sales Lead"), + ("maya", "Maya Ford", "WORKER", "sales", "Account Executive"), + ("pavel", "Pavel Neri", "WORKER", "sales", "Growth Analyst"), + ("sue", "Sue Calder", "WORKER", "sales", "Customer Success"), + ("dana", "Dana Moore", "WORKER", "product", "Product Operations / Finance"), + ("lena", "Lena Vale", "WORKER", "product", "Product Designer"), + ("iris", "Iris Reed", "WORKER", "product", "Product Research"), + ("owen", "Owen Park", "WORKER", "product", "Product Manager"), + ("cara", "Cara Ionescu", "WORKER", "engineering", "Engineering Lead"), + ("eli", "Eli Hart", "WORKER", "engineering", "Backend Engineer"), + ("felix", "Felix Rowan", "WORKER", "engineering", "Quality Engineer"), +) +AUTHOR_BY_DECISION = { + "dec_jan_smb": "alice", + "dec_mar_pause_paid": "bob", + "dec_jun_hiring_freeze": "dana", + "dec_sep_resume_paid": "bob", + "dec_nov_deprecate_v1": "owen", +} +FAKE_SOURCES = ( + # id, kind, title, timestamp, scope, source_authority_id, source_ref + ("ev_demo_sales_mail", "email", "Sales email: partners request a smaller pilot", + "2025-03-16T09:00:00Z", "team:sales", "auth_drive", "synthetic://mail/sales-mar"), + ("ev_demo_eng_task", "task", "Task ENG-104: review onboarding regression", + "2025-04-22T10:00:00Z", "team:engineering", "auth_github", "synthetic://tasks/ENG-104"), + ("ev_demo_sales_task", "task", "Task SALES-17: qualify partner opportunities", + "2025-09-16T08:00:00Z", "team:sales", "auth_crm", "synthetic://tasks/SALES-17"), + ("ev_demo_product_mail", "email", "Product email: friction in trial activation", + "2025-10-12T10:00:00Z", "team:product", "auth_drive", "synthetic://mail/product-oct"), +) +FAKE_EVENTS = ( + ("evt_demo_sales_mail", "Sales reviewed fictional partner requests", + "2025-03-17T10:00:00Z", "team:sales", "ent_sales", "ev_demo_sales_mail"), + ("evt_demo_eng_task", "Engineering assigned onboarding regression review", + "2025-04-23T13:00:00Z", "team:engineering", "ent_eng", "ev_demo_eng_task"), + ("evt_demo_sales_task", "Sales started partner qualification task", + "2025-09-16T12:00:00Z", "team:sales", "ent_sales", "ev_demo_sales_task"), + ("evt_demo_product_mail", "Product collected synthetic trial feedback", + "2025-10-13T12:00:00Z", "team:product", "ent_product_team", "ev_demo_product_mail"), +) + + +def build_demo_bundle() -> dict[str, Any]: + """Decorate the already-qualified 2025 P2A fixture; P2C/P2D do actual governance.""" + memory = deepcopy(load_dataset(BASE_FIXTURE)) + if memory["organization"].get("synthetic") is not True: + raise ValueError("DEMO_REQUIRES_SYNTHETIC_ORGANIZATION") + if (memory["period"]["start"], memory["period"]["end"]) != DEMO_PERIOD: + raise ValueError("DEMO_SOURCE_PERIOD_DRIFT") + + names = {pid: name for pid, name, *_ in PEOPLE} + original = {item["id"]: item for item in memory["principals"]} + if set(original) != {"alice", "bob", "cara", "dana"}: + raise ValueError("DEMO_SOURCE_PRINCIPALS_DRIFT") + principals = [] + actors = [] + grants = [] + all_scopes = ["company", "team:sales", "team:product", "team:engineering", + "restricted:finance"] + for pid, name, role, team, title in PEOPLE: + scopes = list(all_scopes) if role == "DIRECTOR" else ["company", "team:" + team] + if pid == "dana": + scopes.append("restricted:finance") + principals.append({"id": pid, "name": name, "role": role, "scopes": scopes}) + actor = {"id": "actor:" + pid, "principal_id": pid, "actor_kind": "HUMAN", + "role": role, "name": name, "job_title": title, "scopes": list(scopes)} + if team: + actor.update(team_id=team, manager_actor_id="actor:alice") + actors.append(actor) + grants.append({"id": "grant:" + pid, "actor_id": actor["id"], + "actions": sorted(ACTIONS) if role == "DIRECTOR" else ["READ", "PROPOSE"], + "scopes": list(scopes)}) + + robot_id = "agent_atlas" + principals.append({"id": robot_id, "name": "Atlas Research Agent", + "role": "AGENT", "scopes": ["company", "team:engineering"]}) + actors.append({"id": "actor:" + robot_id, "principal_id": robot_id, + "name": "Atlas Research Agent", "actor_kind": "AGENT", "role": "AGENT", + "team_id": "engineering", "scopes": ["company", "team:engineering"], + "manager_actor_id": "actor:cara"}) + memory["principals"] = principals + + for decision in memory["decisions"]: + pid = AUTHOR_BY_DECISION.get(decision["id"]) + if pid is None: + raise ValueError("DEMO_UNATTRIBUTED_DECISION") + decision["decided_by_principal"] = pid + decision["decided_by_name"] = names[pid] + # The historical rationale and evidence references stay unchanged. + + for rid, kind, title, when, scope, authority, ref in FAKE_SOURCES: + memory["evidence"].append({"id": rid, "kind": kind, "title": title, + "recorded_at": when, "scope": scope, "truth_class": "EVIDENCE", + "source_authority_id": authority, "source_ref": ref}) + for rid, title, when, scope, entity, evidence in FAKE_EVENTS: + memory["events"].append({"id": rid, "title": title, "occurred_at": when, + "scope": scope, "truth_class": "FACT", "entity_ids": [entity], + "evidence_ids": [evidence]}) + + policy = {"schema_version": "company-twin-p2c/1", "tenant_id": TENANT, + "actors": actors, "grants": grants, + "delegations": [{ + "id": "delegation:atlas-research", + "grantor_actor_id": "actor:alice", + "grantee_actor_id": "actor:" + robot_id, + "actions": ["READ", "PROPOSE"], "scopes": ["team:engineering"], + "parent_id": None, "expires_at": "2026-01-01T00:00:00Z", + "revoked": False, + }], "explicit_denies": [], "revoked_delegation_ids": []} + bundle = {"schema_version": "company-twin-console-bundle/1", + "memory": memory, "policy": policy, + "proposals": [{ + "id": "proposal:atlas-regression", + "actor_principal_id": robot_id, "scope": "team:engineering", + "classification": "INTERNAL", "created_at": "2025-09-20T10:00:00Z", + "status": "PENDING_HUMAN_REVIEW", + "summary": "Suggest a regression checklist for onboarding. No action executed.", + }], + "runtime": {"mode": "SYNTHETIC_LOCAL_DEMO", "read_only": True, + "twin_baseline": "P2A-P2D", "execution_authority": "NONE", + "can_execute": False}} + validate_bundle(bundle) + if (sum(actor["actor_kind"] == "HUMAN" for actor in actors) != 12 + or {actor["team_id"] for actor in actors if "team_id" in actor} != + {"sales", "product", "engineering"}): + raise ValueError("DEMO_ORGANIZATION_SHAPE_INVALID") + return bundle + + +class ClientDemoHandler(_ConsoleHandler): + """Reuse the existing GET /api/snapshot and HTTP write-deny policy unchanged.""" + + def _send_html(self, *, head_only: bool = False) -> None: + body = HTML_FILE.read_bytes() + self.send_response(HTTPStatus.OK) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.send_header("Cache-Control", "no-store") + self.send_header("X-Content-Type-Options", "nosniff") + self.send_header("Content-Security-Policy", + "default-src 'none'; connect-src 'self'; " + "script-src 'unsafe-inline'; style-src 'unsafe-inline'; " + "base-uri 'none'; form-action 'none'") + self.end_headers() + if not head_only: + self.wfile.write(body) + + +def make_demo_server(*, host: str = "127.0.0.1", port: int = DEMO_PORT): + server = make_server(host=host, port=port, bundle=build_demo_bundle()) + server.RequestHandlerClass = ClientDemoHandler + return server + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Fictional Northstar Labs Company Twin showcase") + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=DEMO_PORT) + parser.add_argument("--check", action="store_true", help="Offline smoke; do not start HTTP") + args = parser.parse_args(argv) + if args.check: + bundle = build_demo_bundle() + early = build_snapshot(bundle, principal_id="alice", as_of="2025-03-31T23:59:59Z") + late = build_snapshot(bundle, principal_id="alice", as_of="2025-12-31T23:59:59Z") + robot = build_snapshot(bundle, principal_id="agent_atlas", as_of="2025-12-31T23:59:59Z") + print(json.dumps({"status": "PASS", "synthetic": True, + "human_count": 12, "teams": 3, + "early_decisions": early["counts"]["decisions"], + "late_decisions": late["counts"]["decisions"], + "agent_can_execute": robot["capabilities"]["EXECUTE"]["allowed"]}, + sort_keys=True)) + return 0 + server = make_demo_server(host=args.host, port=args.port) + print(f"SYNTHETIC DEMO ONLY · http://{args.host}:{server.server_address[1]}/") + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/COMPANY_TWIN_CLIENT_DEMO_R1.md b/docs/COMPANY_TWIN_CLIENT_DEMO_R1.md new file mode 100644 index 00000000..936b5e1f --- /dev/null +++ b/docs/COMPANY_TWIN_CLIENT_DEMO_R1.md @@ -0,0 +1,84 @@ +# Company Twin Client Demo R1 — Northstar Labs (FICTIONAL) + +**Status:** local synthetic-only client showcase candidate / not production admitted. +**Git baseline:** protected master `517c7b48e270a3077e53d890e907c9cb2b3ce491` (tree `fcb37ef8d9af2c349a62c576b5634798881a7fbe`). +**Scope:** one isolated demo branch and one Draft PR; no merge, deploy, scheduler/TPM, live connectors, real model, external API, or real data. + +## Product in one sentence + +ContinuityOS reconstructs a company's evidence-backed history and decisions at a selected point in time, with Director / Worker / Managed-Agent access constraints. The example company **Northstar Labs** is entirely fictional. + +## What this actually uses + +- **P2A** `continuityos.company_twin.load_dataset/replay` and the existing `northstar_labs_2025.json` 12-month synthetic fixture. +- **P2C** `continuityos.company_twin_policy.evaluate` via the existing P2D `build_snapshot`: tenant/scope/role/delegation checks. +- **P2D** `continuityos.company_twin_console.make_server` and its existing GET `/api/snapshot`, HEAD, HTTP-405 write denials, loopback-only bind and unchanged policy filtering. +- **New demo-only presentation adapter:** `demo/company_twin_client_demo.py`, standalone HTML page, synthetic actor/grant metadata, synthetic emails/tasks, human-authored demo decision attribution. +- **P2B ingestion and P2E live/self-pilots are not executed**. The new synthetic email/task records are P2A-compatible fixtures, not real connectors. +- **Person Twin #211 is already merged**; demo does not consume new #212–#215 features or modify the Person Twin production/evidence path. + +This PR adds NO new memory/policy/agent/runtime core and modifies NO existing tracked files. + +## 10-minute client walkthrough + +| Time | Action | Observable result | +| --- | --- | --- | +| 0–1 min | Open Northstar Labs view as **Alice Chen / Director** | 12 fictional humans, 3 teams, one managed research agent | +| 1–3 min | Scroll the company timeline | Synthetic documents, email/task events, decisions and outcomes covering 2025 | +| 3–5 min | Open Decision Ledger | For each authorized decision: date, fictional decision author, rationale, evidence title(s), ACTIVE/SUPERSEDED status | +| 5–7 min | Click **Mar 31**, then **Dec 31** | Replayed authorized decision/event set changes; March's paid-acquisition pause becomes SUPERSEDED by the September partner-qualified decision | +| 7–9 min | Switch **Director → Bob (Sales) → Cara (Engineering) → Atlas Agent** | Sales cannot see restricted finance or engineering; agent sees scoped engineering data and READ/PROPOSE but not APPROVE/EXECUTE | +| 9–10 min | Show pending Atlas proposal and policy status | Proposal needs human review; no external action, approval, or execution endpoint exists | + +Dates are selectable within **2025**, the period of the provided historical dataset. Dates outside this dataset are not claimed to be supported. + +## Local demo commands (no cloud Actions minutes) + +From a clean verified source checkout on Windows or Linux: + +```powershell +python -B -m demo.company_twin_client_demo --check +python -B -m demo.company_twin_client_demo +``` + +Open `http://127.0.0.1:8769/`. Use Ctrl+C to stop. Server refuses non-loopback bindings. Port may be changed with `--port 0` for tests or `--port 8770` for another local session. + +Bounded local tests, without auto-loading extra pytest plugins and without writing cache or bytecode: + +```powershell +$env:PYTHONDONTWRITEBYTECODE='1' +$env:PYTEST_DISABLE_PLUGIN_AUTOLOAD='1' +python -B -m pytest -q -p no:cacheprovider tests/test_company_twin_client_demo.py tests/test_company_twin.py tests/test_company_twin_policy.py tests/test_company_twin_console.py tests/test_company_twin_ingest.py +``` + +No need for Docker, browser extensions, SSH, API keys, a paid model, or credentials. + +## Truth and authorization claims + +- **SYNTHETIC DATA ONLY**. Names, company, emails, tasks, source references, roles and decision authors are made up. +- All evidence is marked `EVIDENCE`, history is `FACT`, interpretation stays `INFERENCE`. +- Decision `decided_by_name` and `decided_by_principal` are **fictional demo metadata**; they are not cryptographically signed, authentic user identities, verified approvals, or a proof of source authorship. +- The role selector **simulates viewpoints; it is not authentication**. It is never advertised as a production identity/SSO gate. +- P2A's replay covers only existing dates and applies explicit record scope. P2D intersects P2A with P2C and hides unauthorized rows, IDs and decision lineage. +- Agent is assigned a named human manager plus an explicit Director-issued, time-limited delegation to `team:engineering`; `READ/PROPOSE` are authorized *policy classes*, not HTTP mutation endpoints. +- Every actual HTTP write method (`POST/PUT/PATCH/DELETE`) is rejected. `EXECUTE` does not exist as a P2C action, and `can_execute=false` in runtime and governance outputs. +- External network isolation is a *design boundary of the demo package*, not an OS-level sandbox claim. Server listens only on loopback; the code never fetches real sources or calls LLMs. +- No real ContinuityOS, Robert, client, Gmail, Drive, Slack, GitHub, finance, secrets, private repos or other protected data enter the fixture. + +## Verification and release + +A customer demo should only be called ready after deterministic local tests and a live local 10-minute walkthrough (browser visual quality / client presentation) are checked. An offline HTTP smoke is not a production penetration test. + +Actions budget is **$0 until 2026-11-01**, so the proposed Draft PR should be created with a `[skip ci]` commit marker. That is **not a successful CI run**; full Ubuntu/Windows/CodeQL gates must run later if budget permits and before any separately authorized merge. Do not bypass protected-master requirements. No manual workflows, push to existing release branches, merge or deployment are authorized by this demo. + +**Estimate (engineering effort, not elapsed guarantee):** 1–2 focused days for presentation, fixtures, tests and Draft PR; 0.5–1 additional day for recorded browser walkthrough, visual fixes and full CI when available. Time to publication depends on available approvals and Actions budget. + +## Stop conditions + +- Git base/head tree changes unexpectedly, or tracked files outside the four new demo/test/docs files change. +- Any real input, production authorization, external connection or delegated agent EXECUTE emerges. +- A non-loopback host is requested. +- Any P2A/P2C/P2D permission regression is observed. +- CI cannot run: keep as Draft and clearly label CI UNEXECUTED. Never call this production-qualified. + +Unchanged / on HOLD: H3 PR #209/#210, Person Twin PR #212–#215, production admission C0/T11/T12 and all capital/TPM/Scheduler authority. diff --git a/tests/test_company_twin_client_demo.py b/tests/test_company_twin_client_demo.py new file mode 100644 index 00000000..8d6ef906 --- /dev/null +++ b/tests/test_company_twin_client_demo.py @@ -0,0 +1,183 @@ +"""Bounded offline tests for the fictional Northstar Labs client walkthrough.""" +from __future__ import annotations + +import json +from pathlib import Path +import threading +from urllib.error import HTTPError +from urllib.request import Request, urlopen + +import pytest + +from continuityos.company_twin_console import CompanyTwinConsoleError, build_snapshot +from demo.company_twin_client_demo import ( + BASE_FIXTURE, HTML_FILE, PEOPLE, build_demo_bundle, make_demo_server, +) + + +@pytest.fixture(scope="module") +def bundle(): + return build_demo_bundle() + + +def snapshot(bundle, principal: str, at: str = "2025-12-31T23:59:59Z"): + return build_snapshot(bundle, principal_id=principal, as_of=at) + + +def test_fixture_is_pinned_2025_synthetic_and_has_no_real_connectors(bundle): + assert BASE_FIXTURE.name == "northstar_labs_2025.json" + assert bundle["memory"]["organization"]["synthetic"] is True + assert bundle["runtime"]["read_only"] is True + assert bundle["runtime"]["can_execute"] is False + assert bundle["runtime"]["execution_authority"] == "NONE" + assert bundle["memory"]["period"] == { + "start": "2025-01-01T00:00:00Z", "end": "2025-12-31T23:59:59Z" + } + assert all("synthetic" in record["source_ref"] + for record in bundle["memory"]["evidence"]) + assert all(not str(p["id"]).startswith("principal_") for p in bundle["memory"]["principals"]) + + +def test_12_humans_three_teams_and_one_supervised_agent(bundle): + policy = bundle["policy"] + humans = [actor for actor in policy["actors"] if actor["actor_kind"] == "HUMAN"] + agents = [actor for actor in policy["actors"] if actor["actor_kind"] == "AGENT"] + assert len(PEOPLE) == len(humans) == 12 + assert len(agents) == 1 + assert [a["role"] for a in humans].count("DIRECTOR") == 1 + assert {p["team_id"] for p in humans if "team_id" in p} == { + "sales", "product", "engineering" + } + assert agents[0]["manager_actor_id"] == "actor:cara" + assert len(bundle["memory"]["principals"]) == 13 + assert len(policy["delegations"]) == 1 + assert set(policy["delegations"][0]["actions"]) == {"READ", "PROPOSE"} + assert policy["delegations"][0]["scopes"] == ["team:engineering"] + + +def test_historical_replay_changes_and_decision_lineage_is_authorized(bundle): + march = snapshot(bundle, "alice", "2025-03-31T23:59:59Z") + december = snapshot(bundle, "alice") + assert march["counts"]["decisions"] == 2 + assert december["counts"]["decisions"] == 5 + assert march["counts"]["events"] < december["counts"]["events"] + assert any(d["id"] == "dec_mar_pause_paid" and d["replay_status"] == "ACTIVE" + for d in march["decisions"]) + assert any(d["id"] == "dec_mar_pause_paid" and d["replay_status"] == "SUPERSEDED" + for d in december["decisions"]) + assert december["decision_lineages"]["dec_sep_resume_paid"] == [ + "dec_sep_resume_paid", "dec_mar_pause_paid" + ] + names = {p["id"]: p["name"] for p in bundle["memory"]["principals"]} + for d in december["decisions"]: + assert d["decided_by_name"] == names[d["decided_by_principal"]] + assert d["rationale"] and d["evidence_ids"] + assert december["organization"]["synthetic"] is True + + +def test_sales_cannot_read_engineering_or_restricted_finance(bundle): + director = snapshot(bundle, "alice") + sales = snapshot(bundle, "bob") + evidence_dir = {r["id"] for r in director["evidence"]} + evidence_sales = {r["id"] for r in sales["evidence"]} + assert "ev_jun_cash" in evidence_dir + assert "ev_jun_cash" not in evidence_sales + assert "ev_demo_eng_task" not in evidence_sales + assert "ev_demo_sales_mail" in evidence_sales + assert "ev_demo_sales_task" in evidence_sales + assert all(not item["scope"].startswith("restricted:") for item in sales["decisions"]) + + +def test_agent_delegation_is_read_propose_only_and_managed_by_human(bundle): + agent = snapshot(bundle, "agent_atlas") + assert agent["actor"]["actor_kind"] == "AGENT" + assert agent["actor"]["manager"]["name"] == "Cara Ionescu" + assert agent["capabilities"]["READ"]["allowed"] is True + assert agent["capabilities"]["PROPOSE"]["allowed"] is True + for action in ("APPROVE", "DELEGATE", "REVOKE", "DELETE", "EXPORT", "EXECUTE"): + assert agent["capabilities"][action]["allowed"] is False + assert agent["governance"] == { + "execution_authority": "NONE", + "can_execute": False, + "can_trade": False, + "capital_permission": "DENY", + } + assert len(agent["proposals"]) == 1 + assert agent["proposals"][0]["status"] == "PENDING_HUMAN_REVIEW" + assert all(x["scope"] == "team:engineering" for x in agent["evidence"]) + assert not agent["lifecycle_previews"] + + +def test_no_agent_authorization_after_delegation_expiry(bundle): + # P2C evaluates expiry at the requested time; the demo itself ends in 2025. + changed = json.loads(json.dumps(bundle)) + changed["policy"]["delegations"][0]["expires_at"] = "2025-07-01T00:00:00Z" + denied = snapshot(changed, "agent_atlas") + assert denied["capabilities"]["READ"]["allowed"] is False + assert denied["capabilities"]["PROPOSE"]["allowed"] is False + assert denied["counts"]["evidence"] == 0 + assert denied["proposals"] == [] + + +def test_director_worker_and_robot_see_different_actor_graph(bundle): + director = snapshot(bundle, "alice") + worker = snapshot(bundle, "bob") + robot = snapshot(bundle, "agent_atlas") + assert len(director["organization_graph"]["nodes"]) == 13 + assert len(worker["organization_graph"]["nodes"]) < 13 + names = {n["name"] for n in robot["organization_graph"]["nodes"]} + assert "Cara Ionescu" in names and "Atlas Research Agent" in names + assert "Dana Moore" not in names + + +def test_source_aware_synthetic_email_and_task_timeline(bundle): + dates = {x["id"] for x in snapshot(bundle, "alice")["timeline"]} + assert "evt_demo_eng_task" in dates + assert "evt_demo_product_mail" in dates + assert "evt_demo_sales_task" in dates + kinds = {r["kind"] for r in bundle["memory"]["evidence"]} + assert {"email", "task"}.issubset(kinds) + + +def test_no_external_dependencies_or_unmarked_demo_screen(): + html = HTML_FILE.read_text(encoding="utf-8") + assert "Synthetic demo" in html and "Fictional company" in html + assert "not user authentication" in html.lower() or "not user authentication" in html + assert "textContent" in html + assert "