Skip to content

Commit 601dc6c

Browse files
committed
feat(identity): recover expired laptop credential
1 parent d5a14f2 commit 601dc6c

41 files changed

Lines changed: 3241 additions & 100 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,16 @@ harness. The user restarts it explicitly; connection is recorded only after a
221221
new measured process proves the expected adapter generation and exact enrolled
222222
harness binding.
223223

224+
If that laptop credential expires while the exact owner-private identity and
225+
P-256 key remain intact, use `agentnet credential reauthorize-expired`.
226+
The package proves the expired binding directly, obtains fresh owner WebAuthn
227+
UV, and creates one same-key successor at epoch +1 without enrollment,
228+
authority, scope, membership, capability, or harness restart. The first call
229+
may report `approval_pending` while opening the stable Approval page; rerun the
230+
identical command after approval. Owner-only state makes response-loss retries
231+
converge. Revoked, rotated, active, compromised, cross-binding, drifted, or
232+
ambiguous credentials fail closed and require their own workflow.
233+
224234
Friendly recipient input is not an address or authority claim. The authenticated,
225235
non-enumerating resolver returns one `ResolvedEndpoint`: exact harness, safe
226236
display metadata, and current scope ID. Zero, multiple, stale, revoked, or
@@ -441,7 +451,7 @@ always-on deployment—see the [implementation guide](docs/implementation-guide.
441451
## Project status
442452

443453
AgentNet is an early public implementation; the latest published package is
444-
`0.1.45`. Its publication does not promote any requirement or gate.
454+
`0.1.50`. Its publication does not promote any requirement or gate.
445455
Published `0.1.29` repaired owner/enrollment OIDC callback parsing after real
446456
Google owner login exposed rejection of valid unique response extensions;
447457
published `0.1.30` repaired installed-verifier package custody; published
@@ -543,6 +553,17 @@ addition to the existing installed journey. These are local and CI evidence,
543553
not production certification, and no requirement or must-not-ship gate is
544554
promoted.
545555

556+
Candidate `0.1.51` adds one package-owned recovery lane for an expired laptop
557+
credential when the exact owner-private identity profile and OS- or
558+
hardware-bound P-256 key remain intact. Dedicated expired-credential DPoP
559+
routes bind the unchanged domain, principal, harness, key, assurance, and
560+
profile to fresh owner WebAuthn UV, then atomically retire the expired epoch and
561+
issue the exact same-key successor at epoch +1. Resumable owner-only CLI state
562+
preserves the transaction across approval timeout or response loss. The flow
563+
does not re-enroll, rotate keys, restore revoked actors, grant authority, change
564+
communication scopes or memberships, or restart the harness. No requirement,
565+
production claim, or must-not-ship gate is promoted.
566+
546567
Git tag `v0.1.23` reached the staging workflow, but CI stopped before npm
547568
staging because one hermetic interruption test mocked `/usr/bin/useradd` on a
548569
runner where that path did not exist. No `0.1.23` package was staged or

RELEASE_MANIFEST.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@
218218
"path": "uv.lock",
219219
"pyproject": {
220220
"path": "pyproject.toml",
221-
"sha256": "95c9effe37951f5ab012aa00743ecc52b44ca0ac0b62ab08eddafa3ab7559e7a"
221+
"sha256": "6aebcdea17d2715c3136c2b6b3179a424a3eed990f54d65a4cccbf67c715c5da"
222222
},
223223
"resolution": {
224224
"a2a-sdk": "1.1.0",
@@ -285,7 +285,7 @@
285285
"webauthn": "3.0.0"
286286
},
287287
"revision": 3,
288-
"sha256": "afffa3558290939ca4f3efae1b04559db3299084f4a62c0d93ccdd4ab66e69c8"
288+
"sha256": "c971fdea8d3782a9d7349b5723f4e036740d314f19f7c3553dc6ba1780c9f84d"
289289
},
290290
"external_release_evidence": {
291291
"installer_lifecycle": {
@@ -529,20 +529,20 @@
529529
"release": {
530530
"name": "agentnet",
531531
"production_ready": false,
532-
"profile": "setup_usability_candidate",
533-
"reason": "All 19 must-not-ship gates remain non-PASSED; local implementation evidence is partial and required external, privileged, production-topology, or owner evidence remains absent.",
532+
"profile": "expired_laptop_credential_reauthorization_candidate",
533+
"reason": "All 19 must-not-ship gates remain non-PASSED; local evidence proves the package-owned same-binding expired-laptop credential reauthorization contract, but fresh installed-host, live owner WebAuthn/Core recovery, and required external, privileged, production-topology, and owner evidence remain absent.",
534534
"ship_eligible": false,
535535
"status": "BLOCKED",
536-
"version": "0.1.50"
536+
"version": "0.1.51"
537537
},
538538
"release_inputs": {
539539
"README.md": {
540540
"path": "README.md",
541-
"sha256": "f6839282457daeeb8cad2b76621b384725cb6caac15e92ad3e09f72633fcbbe3"
541+
"sha256": "c98f3fa0cce833f8166fb938281e68f1a44134d2d305e97372b0a00968b1fae9"
542542
},
543543
"REQUIREMENTS_STATUS.md": {
544544
"path": "REQUIREMENTS_STATUS.md",
545-
"sha256": "59491a96c8697c41c47d29756144768f5b62da0df04c3d75c9579dc762dae0d8"
545+
"sha256": "c7a57046e2b352ca4296bc7753fd7b783a0d8dd0f253708d561c1e2c54ce663e"
546546
},
547547
"deploy/Dockerfile": {
548548
"path": "deploy/Dockerfile",
@@ -562,11 +562,11 @@
562562
},
563563
"docs/GATE_EVIDENCE.md": {
564564
"path": "docs/GATE_EVIDENCE.md",
565-
"sha256": "1d690b65756169e1954c1a37c0436bc3dc053e903e61dd4a647a0a0f786be6e2"
565+
"sha256": "51695ac55e5cbd41be39809b568ac78051eb1a91ac5aafe1b380bffc6af3325c"
566566
},
567567
"docs/RELEASE_MANIFEST.md": {
568568
"path": "docs/RELEASE_MANIFEST.md",
569-
"sha256": "99fb7ad44dfb05766c59ac69c1decb1df9e951984c7196acb53c37179d2d99ad"
569+
"sha256": "c091bd6ac72357c3d4b923638244913a5b63a1e59c64c9509161b3c4fba4d9dd"
570570
},
571571
"evidence/gates/G01/2026-07-13-installed-harnesses/manifest.json": {
572572
"path": "evidence/gates/G01/2026-07-13-installed-harnesses/manifest.json",
@@ -602,13 +602,13 @@
602602
},
603603
"scripts/verify_release.py": {
604604
"path": "scripts/verify_release.py",
605-
"sha256": "b559a98eb5050f54a9d4d222ef83e2b6bda16af5c79860699d0eace2c1661d00"
605+
"sha256": "fd3a11fbb1473c5a32d970d8bc102028b211c5ac0ace67412dfb8eac8bfa60a4"
606606
}
607607
},
608608
"release_source_tree": {
609609
"algorithm": "sha256(path NUL bytes NUL)",
610610
"path": "src",
611-
"sha256": "740adfb28e27641c3ec0bab470036c4690fde2c70a53ab0568fde644aecddb7e"
611+
"sha256": "a3c1e64482bb7e0751dcbb3fb1a05d831f1b9d425b6063eca0a4c2a92612901d"
612612
},
613613
"runtime": {
614614
"implementation": "CPython",
@@ -746,5 +746,5 @@
746746
},
747747
"version": "1.0"
748748
},
749-
"snapshot_date": "2026-08-07"
749+
"snapshot_date": "2026-08-14"
750750
}

REQUIREMENTS_STATUS.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Requirements Status
22

3-
Snapshot: 2026-08-07. This is an implementation/evidence ledger, not a release
3+
Snapshot: 2026-08-14. This is an implementation/evidence ledger, not a release
44
certificate. It contains the exact 85 stable requirement IDs from the preserved
55
requirements reference. PD-001 through PD-011 are listed separately because
66
they are accountable policy decisions, not additional requirements.
@@ -428,6 +428,29 @@ published and historical release evidence:
428428
`UX-001`, `UX-002`, `SEC-003`, `SEC-005`, `OPS-003`, and `OPS-006`. No
429429
requirement or must-not-ship gate is promoted.
430430

431+
- Candidate `0.1.51` adds a dedicated, package-owned reauthorization workflow
432+
for an expired ordinary laptop credential. It preserves the exact
433+
domain/principal/harness binding, OS- or hardware-bound public key, authority,
434+
scopes, memberships, capabilities, and credential-supersession history;
435+
requires fresh independent WebAuthn user verification; rejects active,
436+
revoked, mismatched, non-laptop, stale, replayed, expired-transaction, and
437+
conflicting bindings before key use or mutation; and commits exactly one
438+
same-key successor epoch with idempotent response-loss recovery. It grants no
439+
authority and performs no enrollment, generic renewal, key replacement, or
440+
service restart. Current evidence is hermetic only: the focused lane reports
441+
**698 passed and 5 expected dedicated-PostgreSQL skips**, the broad
442+
releasable-source lane reports **2197 passed and 21 expected
443+
platform/dedicated-PostgreSQL skips**, two independent release builds are
444+
byte-identical, and source plus two recursive packed generations each report
445+
**2224 passed and 21 expected platform/dedicated-PostgreSQL skips**. The
446+
packaged exact-endpoint routing gate, packaged `0.1.45` user journey, and
447+
separate-process communication/obligation roundtrip pass. Fresh
448+
installed-host, live owner WebAuthn/Core recovery, same-commit CI, and
449+
production-topology evidence remain pending. Affected IDs are `ID-006`,
450+
`ID-007`, `ID-009`, `AUTH-001`, `AUTH-002`, `AUTH-004`, `AUTH-007`,
451+
`SEC-003`, `SEC-005`, `SEC-007`, `OPS-003`, and `OPS-006`. No requirement or
452+
must-not-ship gate is promoted.
453+
431454

432455

433456
- S5/S6 directly exercise `ID-006`, `AUTH-001`, `AUTH-002`, `AUTH-003`,
@@ -509,7 +532,7 @@ stable requirement's status or remaining external/owner boundary.
509532
| ID-006 — human and exact harness identities remain distinct | partial-external | `src/agentnet/identity/actors.py`; `src/agentnet/identity/context.py`; `src/agentnet/identity/workload.py`; `src/agentnet/bindings/composition.py` | Actor-union, sibling, workload, current-epoch local binding, and measured IPC child negatives: `tests/identity/test_actor_union.py`; `tests/identity/test_workload_identity.py`; `tests/security/test_ipc_capability.py`; `tests/bindings/test_local_binding_composition.py` | Privileged target-host same-UID/PID-reuse attribution and exact installed-harness evidence remain external. |
510533
| ID-007 — revoke one harness without revoking siblings | partial-external | `src/agentnet/identity/revocation.py`; `src/agentnet/identity_admin_http.py`; `src/agentnet/identity/context.py` | Revocation, wrong approval, concurrent lifecycle, and admin HTTP: `tests/identity/test_revocation.py`; `tests/integration/test_identity_admin_http.py` | Real device-loss/offboarding drill and full cross-resource revocation matrix are absent. |
511534
| ID-008 — identity is scoped to an exact trust domain | partial-external | `src/agentnet/identity/domains.py`; `src/agentnet/identity/context.py`; `src/agentnet/federation/service.py` | Cross-domain proof, pairwise guest, bilateral trust, and revocation negatives: `tests/identity/test_context.py`; `tests/federation/test_bilateral_guest.py`; `tests/federation/test_http_composition.py` | Independently administered partner-domain evidence is absent. |
512-
| ID-009 — issuance, rotation, expiry, recovery, compromise, and offboarding | owner-blocked | `src/agentnet/identity/credentials.py`; `src/agentnet/identity/recovery.py`; `src/agentnet/identity/revocation.py`; `src/agentnet/approval/webauthn_uv.py` | Rotation/recovery/revocation positives, substitutions, replay, races, plus approval credential/request/challenge/receipt expiry and revocation: `tests/identity/test_credential_rotation.py`; `tests/identity/test_recovery.py`; `tests/identity/test_revocation.py`; `tests/approval/test_webauthn_service.py` | Real custody/recovery administrators, approval signer/authenticator rotation drill, and signed PD-005/009 lifecycle choices are absent. |
535+
| ID-009 — issuance, rotation, expiry, recovery, compromise, and offboarding | owner-blocked | `src/agentnet/identity/credentials.py`; `src/agentnet/identity/recovery.py`; `src/agentnet/identity/revocation.py`; `src/agentnet/identity/context.py`; `src/agentnet/http_auth.py`; `src/agentnet/core/app.py`; `src/agentnet/product_http.py`; `src/agentnet/client.py`; `src/agentnet/cli.py`; `src/agentnet/approval/webauthn_uv.py`; `src/agentnet/approval/transaction_summary.py`; `src/agentnet/security/signatures.py` | Rotation/recovery/revocation positives, substitutions, replay, races, approval credential/request/challenge/receipt expiry and revocation, and same-binding expired laptop reauthorization: `tests/identity/test_credential_rotation.py`; `tests/identity/test_recovery.py`; `tests/identity/test_revocation.py`; `tests/identity/test_laptop_credential_reauthorization.py`; `tests/cli/test_laptop_credential_reauthorization_cli.py`; `tests/approval/test_webauthn_service.py`; `tests/approval/test_transaction_summary.py` | Real custody/recovery administrators, approval signer/authenticator rotation drill, and signed PD-005/009 lifecycle choices are absent. |
513536
| AUTH-001 — every protected operation resolves a verified caller | partial-external | `src/agentnet/identity/context.py`; `src/agentnet/identity/workload.py`; `src/agentnet/authorization/policy.py` | Human, guest, workload, and forged-workload cases: `tests/identity/test_context.py`; `tests/identity/test_workload_identity.py`; `tests/federation/test_bilateral_guest.py`; `tests/delivery/test_mailbox.py` | Privileged transport identity evidence for deployed workloads is absent. |
514537
| AUTH-002 — authorization consumes proof-derived identity | local-tested | `src/agentnet/security/dpop.py`; `src/agentnet/identity/context.py`; `src/agentnet/client.py`; `src/agentnet/approval/internal_broker.py`; `src/agentnet/approval/http.py` | Canonical target/body/audience/replay and client-origin negatives plus exact Core→Approval method/path/body/audience/purpose/key proof cases: `tests/security/test_signatures_and_replay.py`; `tests/security/test_signed_client.py`; `tests/identity/test_context.py`; `tests/approval/test_internal_broker.py`; `tests/approval/test_approval_http.py` | Local proof contracts are executable and fail-closed; deployed workload/TLS assurance remains external. |
515538
| AUTH-003 — positive permissions attach to the human principal | owner-blocked | `src/agentnet/authorization/policy.py`; `src/agentnet/authorization/evidence.py`; `src/agentnet/identity_admin_http.py` | Human-only authority, signed issue/revoke, replay, and stale revision: `tests/authorization/test_policy.py`; `tests/integration/test_identity_admin_http.py`; `tests/authorization/test_authority_bootstrap.py` | Signed PD-001/003 principal and attenuation policy is absent. |

docs/ARCHITECTURE.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ expired/revoked/rotated binding cannot renew or satisfy readiness. These labels
100100
and server capability limits can only narrow process eligibility; protected
101101
operations still derive and authorize their exact caller independently.
102102

103+
An expired laptop credential is not reconstructed as a `VerifiedActor`. Two
104+
purpose-limited routes authenticate only expired-old-credential DPoP for the
105+
same exact laptop binding; a retry may use only its exact retired predecessor.
106+
The transaction binds domain, principal, harness, credential and epoch, expiry,
107+
unchanged key and public-PEM digest, assurance/profile digest, and fresh
108+
WebAuthn-UV Approval. One atomic commit retires the old credential without
109+
extension and creates exactly one same-key successor at epoch +1. It changes no
110+
authority, scope, membership, capability, identity, or harness process. Stable
111+
pending state and the committed result make response-loss retry idempotent.
112+
This package-owned user-level flow is separate from fresh enrollment, active
113+
renewal, generic recovery or key replacement, and managed-server
114+
reauthorization below.
115+
103116
The current candidate narrows recovery for an expired, still-possessed
104117
managed-server key in the communication-only topology. Before first C0,
105118
immutable terminal C0 evidence is the provenance root. After C0, every

0 commit comments

Comments
 (0)