@passioncode-ai/passioncode 0.1.46 → 0.1.48
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/CHANGELOG.md +23 -0
- package/family.json +1 -1
- package/package.json +1 -1
- package/payload/.claude-plugin/marketplace.json +3 -3
- package/payload/manifest.json +11 -10
- package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +2 -2
- package/payload/plugins/fabric-agent-adapter/hooks/hooks.json +43 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +7 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +7 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +10 -7
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +32 -15
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +64 -14
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +9 -5
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +24 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +35 -20
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +77 -9
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +317 -45
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +53 -19
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +107 -16
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +472 -62
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +10 -3
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +217 -0
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +99 -0
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +111 -0
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +1265 -0
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.48 - 2026-10-13
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Fabric Agent Adapter is pinned at `v0.24.0`** (it was `v0.23.0`). Fabric Agent Adapter 0.24.0 (FAA-34) lets both service kits read and write three new kinds of service descriptor:
|
|
8
|
+
- an app's own descriptor, for a desktop app a person starts: installed, never running, with no port;
|
|
9
|
+
- a service Project Observatory's node supervises;
|
|
10
|
+
- a service offered to others.
|
|
11
|
+
|
|
12
|
+
The kits now say who installed a service by role, never by name, and how its model calls sign in, as a kind and never a credential. A host refuses a request above an offered service's highest data class. Fabric's button shows only while Fabric itself is installed. The contract pin stays `fe55fc6`.
|
|
13
|
+
|
|
14
|
+
## 0.1.47 - 2026-10-13
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Fabric Agent Adapter is pinned at `v0.23.0`** (it was `v0.21.0`). It brings two adapter releases.
|
|
19
|
+
- **0.22.0 (FAA-12a)** adds the fourth skill, `working-with-fabric`, with the plugin's session hooks. A coding agent opened in a terminal can then work in a project through Fabric's project board.
|
|
20
|
+
- **0.23.0 (FAA-32)** moves the adapter's contract pin to `fe55fc6`:
|
|
21
|
+
- a service's supervisor can mark itself with an argument as well as the environment. The Task Scheduler and Project Observatory's node are now recognised, and markers that disagree stop the service before it takes its lock;
|
|
22
|
+
- a delegation key the estate record certifies follows the contract's verdict and refusal codes;
|
|
23
|
+
- an estate record may be issued up to 60 s ahead of the clock, and its passkey must sign under a `sign.<domain>` RP;
|
|
24
|
+
- Windows token files are checked against the contract's reader rule.
|
|
25
|
+
|
|
3
26
|
## 0.1.46 - 2026-10-12
|
|
4
27
|
|
|
5
28
|
### Added
|
package/family.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/passioncode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.48",
|
|
4
4
|
"description": "The PassionCode.ai agent skill set \u2014 Fabric Agent Adapter, Observatory Log and the organisation's working rules \u2014 for Claude Code and every other agent on the machine, updated as one set.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"passioncode": "bin/passioncode.js"
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"name": "fabric-agent-adapter",
|
|
12
12
|
"displayName": "Fabric Agent Adapter",
|
|
13
13
|
"source": "./plugins/fabric-agent-adapter",
|
|
14
|
-
"version": "0.
|
|
15
|
-
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
|
|
14
|
+
"version": "0.24.0",
|
|
15
|
+
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "PassionCode.ai",
|
|
18
18
|
"url": "https://passioncode.ai/"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"name": "passioncode",
|
|
36
36
|
"displayName": "PassionCode.ai",
|
|
37
37
|
"source": "./plugins/passioncode",
|
|
38
|
-
"version": "0.1.
|
|
38
|
+
"version": "0.1.48",
|
|
39
39
|
"description": "The organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
40
40
|
"author": {
|
|
41
41
|
"name": "PassionCode.ai",
|
package/payload/manifest.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"family": "passioncode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.48",
|
|
4
4
|
"release": true,
|
|
5
5
|
"members": [
|
|
6
6
|
{
|
|
7
7
|
"name": "fabric-agent-adapter",
|
|
8
8
|
"displayName": "Fabric Agent Adapter",
|
|
9
9
|
"repo": "passioncode-ai/fabric-agent-adapter",
|
|
10
|
-
"ref": "v0.
|
|
11
|
-
"commit": "
|
|
12
|
-
"version": "0.
|
|
10
|
+
"ref": "v0.24.0",
|
|
11
|
+
"commit": "20c0f9ef9bf5fe859b83201b1d854b68325671a7",
|
|
12
|
+
"version": "0.24.0",
|
|
13
13
|
"via": "clone of https://***@github.com/passioncode-ai/fabric-agent-adapter.git",
|
|
14
14
|
"skills": [
|
|
15
15
|
"adapting-projects-to-fabric",
|
|
16
16
|
"building-fabric-services",
|
|
17
|
-
"creating-fabric-agents"
|
|
17
|
+
"creating-fabric-agents",
|
|
18
|
+
"working-with-fabric"
|
|
18
19
|
],
|
|
19
20
|
"legacyPluginIds": [
|
|
20
21
|
"fabric-agent-adapter@fabric-agent-adapter"
|
|
@@ -22,8 +23,8 @@
|
|
|
22
23
|
"legacyMarketplaces": [
|
|
23
24
|
"fabric-agent-adapter"
|
|
24
25
|
],
|
|
25
|
-
"contentHash": "sha256:
|
|
26
|
-
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
|
|
26
|
+
"contentHash": "sha256:41fd37dbc69a876cf56d4fd33d27dfaa6129fa89c6d9df417337d4093427a171",
|
|
27
|
+
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
|
|
27
28
|
"author": {
|
|
28
29
|
"name": "PassionCode.ai",
|
|
29
30
|
"url": "https://passioncode.ai/"
|
|
@@ -61,16 +62,16 @@
|
|
|
61
62
|
"name": "passioncode",
|
|
62
63
|
"displayName": "PassionCode.ai",
|
|
63
64
|
"repo": "passioncode-ai/passioncode",
|
|
64
|
-
"ref": "v0.1.
|
|
65
|
+
"ref": "v0.1.48",
|
|
65
66
|
"commit": null,
|
|
66
|
-
"version": "0.1.
|
|
67
|
+
"version": "0.1.48",
|
|
67
68
|
"via": "this repository",
|
|
68
69
|
"skills": [
|
|
69
70
|
"working-in-passioncode"
|
|
70
71
|
],
|
|
71
72
|
"legacyPluginIds": [],
|
|
72
73
|
"legacyMarketplaces": [],
|
|
73
|
-
"contentHash": "sha256:
|
|
74
|
+
"contentHash": "sha256:6f5b09de32403aaf89a6c8e820efd64b9b9a535e6c5be102c7c638635bb89165",
|
|
74
75
|
"description": "The organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
75
76
|
"author": {
|
|
76
77
|
"name": "PassionCode.ai",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "fabric-agent-adapter",
|
|
4
4
|
"displayName": "Fabric Agent Adapter",
|
|
5
|
-
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
|
|
6
|
-
"version": "0.
|
|
5
|
+
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
|
|
6
|
+
"version": "0.24.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "
|
|
2
|
+
"description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start in a repository whose Fabric dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036). The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start the preamble when Claude Code has the MCP server `fabric` (fabric_whoami, then the project board through com.status and com.list, claim and release), and the machine's Fabric-service count; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose. All silent in a session Fabric launched. Every hook reads no token, prints at most one block, always exits 0, and hands any Fabric call to a detached worker with a 10 s deadline.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -11,6 +11,48 @@
|
|
|
11
11
|
"timeout": 5
|
|
12
12
|
}
|
|
13
13
|
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook session-start",
|
|
20
|
+
"timeout": 5
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"Stop": [
|
|
26
|
+
{
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook stop",
|
|
31
|
+
"timeout": 5
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"SessionEnd": [
|
|
37
|
+
{
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook session-end",
|
|
42
|
+
"timeout": 5
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"UserPromptSubmit": [
|
|
48
|
+
{
|
|
49
|
+
"hooks": [
|
|
50
|
+
{
|
|
51
|
+
"type": "command",
|
|
52
|
+
"command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook user-prompt-submit",
|
|
53
|
+
"timeout": 5
|
|
54
|
+
}
|
|
55
|
+
]
|
|
14
56
|
}
|
|
15
57
|
]
|
|
16
58
|
}
|
|
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
5
5
|
compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.24.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
|
-
contract-commit: "
|
|
10
|
+
contract-commit: "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Adapting projects to Fabric
|
|
@@ -70,7 +70,7 @@ Use exactly:
|
|
|
70
70
|
|
|
71
71
|
- contract version `0.1.0`;
|
|
72
72
|
- repository `https://github.com/passioncode-ai/fabric-agent-contract`;
|
|
73
|
-
- commit `
|
|
73
|
+
- commit `fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0`.
|
|
74
74
|
|
|
75
75
|
This is the default for new bundles. When checking an issued legacy bundle, preserve its
|
|
76
76
|
explicit supported lock and use that exact clean checkout. Never silently upgrade or
|
|
@@ -21,10 +21,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
|
|
|
21
21
|
|
|
22
22
|
CONTRACT_VERSION = "0.1.0"
|
|
23
23
|
CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
|
|
24
|
-
CONTRACT_COMMIT = "
|
|
24
|
+
CONTRACT_COMMIT = "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0"
|
|
25
25
|
# #region supported-contract-revisions — docs: README.md#contract-pin
|
|
26
26
|
# Only this declaration may name a legacy revision in live files.
|
|
27
|
-
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
27
|
+
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
28
28
|
# #endregion supported-contract-revisions
|
|
29
29
|
INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
|
|
30
30
|
MCP_REVISION = "2026-07-28"
|
|
@@ -16,10 +16,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
16
16
|
compatibility: Python 3.9+ or Node.js 20+ for the kits; the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
|
|
17
17
|
metadata:
|
|
18
18
|
author: PassionCode.ai
|
|
19
|
-
version: "0.
|
|
19
|
+
version: "0.24.0"
|
|
20
20
|
contract-version: "0.1.0"
|
|
21
21
|
extension: "fabric-service/0.1"
|
|
22
|
-
extension-commit: "
|
|
22
|
+
extension-commit: "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0"
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
# Building Fabric services
|
|
@@ -323,8 +323,11 @@ this estate needs.
|
|
|
323
323
|
|
|
324
324
|
## When something is missing
|
|
325
325
|
|
|
326
|
-
- **Not macOS / no launchd:**
|
|
327
|
-
service read-only. Keep every other rule.
|
|
326
|
+
- **Not macOS / no launchd:** the kits write a systemd user unit (Linux) or a per-user Scheduled Task (Windows); with
|
|
327
|
+
no supervisor at all declare `lifecycle.manager: "none"`, and hosts show the service read-only. Keep every other rule.
|
|
328
|
+
- **A desktop app with no supervisor** (DEC-0069): its descriptor says `manager: "person"` with `lifecycle.app`, no
|
|
329
|
+
origin, `installedBy {by: "person", role, tool}`; its live origin is in its own liveness file
|
|
330
|
+
(the lifecycle reference, *An app's own descriptor and the node*).
|
|
328
331
|
- **No Python:** use the Node kit; run the probe from any machine that has Python and
|
|
329
332
|
can reach the port, or mark the probe `NOT_RUN` with the reason.
|
|
330
333
|
- **Neither kit fits the framework:** implement the four routes and the lock by hand
|
|
@@ -100,10 +100,13 @@ service. Load it after the stylesheet, with ids only:
|
|
|
100
100
|
— or one shaped like a credential — means no button, never a bad link. A press opens a view in Fabric and starts
|
|
101
101
|
nothing (FB-8).
|
|
102
102
|
- **The host (FB-6):** when the host's `fabric_button` cookie says `owner=host`, the page hides its own button.
|
|
103
|
-
- **Fabric absent (FB-5):** when the cookie says `fabric=absent`, or `data-fabric="absent"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
- **Fabric absent (FB-5):** when the cookie says `fabric=absent`, or `data-fabric="absent"`, or when the page is still
|
|
104
|
+
visible and focused 1.5 s after a link was followed, the menu says "Fabric is not installed — get it at
|
|
105
|
+
passioncode.ai/fabric". Nothing downloads, nothing retries. A local service's server decides `data-fabric` with
|
|
106
|
+
`fabric_button_presence()` / `fabricButtonPresence()`: `present` while Fabric's own descriptor
|
|
107
|
+
(`services/fabric.default.json`, Fabric ADR-0139) is registered and, for an app's descriptor (`manager: "person"`,
|
|
108
|
+
DEC-0069), its app is still installed; `absent` otherwise. A press only follows a link, so the button never reads
|
|
109
|
+
Fabric's liveness file; a caller that sends Fabric anything reads that file and checks the process first.
|
|
107
110
|
|
|
108
111
|
`window.FabricButton` exposes `buildFabricLink`, `parseFabricLink` and `parseButtonCookie` for a page that needs
|
|
109
112
|
them. The button is optional: a page without the script has no button.
|
|
@@ -95,16 +95,19 @@ itself (`isSmallOrderKey`), because Node before 26 lets OpenSSL accept a signatu
|
|
|
95
95
|
|
|
96
96
|
## Keys the estate record certifies
|
|
97
97
|
|
|
98
|
-
A verifier that also reads the estate's record (DEC-0055, [the estate record reference](estate-record.md))
|
|
99
|
-
`kid` only while
|
|
100
|
-
verified revision as `estate=` / `{ estate }`, as a value or as a function returning it,
|
|
98
|
+
A verifier that also reads the estate's record (DEC-0055, DEC-0060, [the estate record reference](estate-record.md))
|
|
99
|
+
trusts a `kid` only while the revision it reads certifies it (DL-14, row 4a, right after the signature). Pass the
|
|
100
|
+
current verified revision as `estate=` / `{ estate }`, as a value or as a function returning it, with
|
|
101
|
+
`estate_applied` / `estateApplied` and `estate_revocations` / `estateRevocations`, to `delegation_gate`,
|
|
101
102
|
`verify_delegation` or `verify_approval`.
|
|
102
|
-
- An uncertified `kid` is
|
|
103
|
+
- An uncertified `kid` is the token's fault: `unauthenticated` (`approval-invalid` for an approval), detail
|
|
103
104
|
`kid-not-certified`.
|
|
104
|
-
-
|
|
105
|
+
- A revision that cannot be read, is outside its window, is lower than the applied one or forks it is the trust
|
|
106
|
+
source's fault: `issuer-unavailable`, detail `estate-unreadable`, `estate-outside-window`, `estate-rolled-back` or
|
|
107
|
+
`estate-forked`.
|
|
105
108
|
- Without `estate`, nothing changes.
|
|
106
|
-
-
|
|
107
|
-
|
|
109
|
+
- Every verifier refuses a clock that is not a number of seconds `unknown-state` `no-clock` before row 1: a NaN clock
|
|
110
|
+
would make every time row false and pass an expired token.
|
|
108
111
|
|
|
109
112
|
## What the kit does not do
|
|
110
113
|
|
|
@@ -43,12 +43,16 @@ It checks, in the order of the ER table:
|
|
|
43
43
|
parent, and no revocation dropped.
|
|
44
44
|
5. **The signer.** The signing passkey must be among the trusted ones and not revoked by the parent. A revision may
|
|
45
45
|
revoke its own signer: that is rotation.
|
|
46
|
-
6. **The assertion.**
|
|
46
|
+
6. **The assertion.** The passkey is a signing credential (DEC-0063): an RP ID that is not a dedicated signing RP,
|
|
47
|
+
`sign.<domain>` in lower case, is `wrong-rp` before the assertion is read, and an origin outside the RP ID is not
|
|
48
|
+
pinned, so a ceremony that ran there is `wrong-origin`.
|
|
47
49
|
- Client data: type, challenge, origin, no cross-origin use.
|
|
48
50
|
- The RP ID hash, and the user-presence and user-verification flags.
|
|
49
51
|
- The signature: ES256 or EdDSA, under a key validated first (a valid P-256 point; an Ed25519 key of full order).
|
|
50
|
-
7. **The window.** At most 30 days.
|
|
51
|
-
|
|
52
|
+
7. **The window.** At most 30 days. A revision may be issued up to 60 s ahead of the verifier's clock (DEC-0066,
|
|
53
|
+
`CLOCK_SKEW_SECONDS`); earlier than that it is `not-yet-valid`.
|
|
54
|
+
8. **The record's own rules.** A violation is `rule-violation` with its `codes`, `FAC-SEM-072…076` and `FAC-SEM-092`
|
|
55
|
+
(each passkey the record lists is a signing credential, its origins within its RP ID).
|
|
52
56
|
|
|
53
57
|
Dates are strict RFC 3339: a `T`, an offset, and a day that exists. `:60` and a space are refused. Passkey JWK `x`
|
|
54
58
|
and `y` must be canonical base64url. The Python kit verifies ES256 in pure Python (P-256, strict DER), and the Node
|
|
@@ -56,22 +60,35 @@ kit through `node:crypto`.
|
|
|
56
60
|
|
|
57
61
|
## Trusting a delegation key
|
|
58
62
|
|
|
59
|
-
A delegation verifier that reads the estate record trusts a token's `kid` only while
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
A delegation verifier that reads the estate record trusts a token's `kid` only while the revision it reads certifies
|
|
64
|
+
it (DL-14, contract DEC-0060): exactly one machine's `hubKey` has that `kid` and the published key's `x`, the clock is
|
|
65
|
+
inside the revision's window (from 60 s before `issuedAt`, DEC-0066, to `validUntil`), and neither the revision nor the
|
|
66
|
+
revocation delta's entries applied since it revoke `hub-key <kid>` or `machine <id>`. It is checked right after the
|
|
67
|
+
signature (DL-13 row 4a). Give the gate the current verified revision, or a function that returns it, the highest
|
|
68
|
+
revision the machine applied, and the delta's revocations:
|
|
62
69
|
|
|
63
70
|
```python
|
|
64
|
-
gate = fd.delegation_gate(audience=..., jwks=keys, issuer=..., effects=..., estate=lambda: current
|
|
71
|
+
gate = fd.delegation_gate(audience=..., jwks=keys, issuer=..., effects=..., estate=lambda: current,
|
|
72
|
+
estate_applied={"revision": applied_number, "digest": applied_digest},
|
|
73
|
+
estate_revocations=lambda: delta_entries)
|
|
65
74
|
```
|
|
66
75
|
|
|
67
|
-
-
|
|
68
|
-
`approval-invalid`) with detail `kid-not-certified`.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
- **The token's fault** — no such certification, another key under that `kid`, a `kid` on two machines, a revoked hub
|
|
77
|
+
key or machine — is `unauthenticated` (for an approval, `approval-invalid`) with detail `kid-not-certified`.
|
|
78
|
+
- **The trust source's fault** is `issuer-unavailable`: `estate-unreadable` (a source that throws or answers nothing
|
|
79
|
+
readable, a revision without a number or digest, a window of zero or over 30 days, dates read strictly, `machines` or
|
|
80
|
+
`revocations` not arrays), `estate-outside-window`, `estate-rolled-back` (lower than the applied revision) and
|
|
81
|
+
`estate-forked` (the applied number with another digest). A caller cannot cure these by minting again.
|
|
82
|
+
- An unreadable clock — not a finite number of seconds a `Date` can hold — is refused `unknown-state` `no-clock`
|
|
83
|
+
before row 1, in every verifier.
|
|
84
|
+
- Only an absent `estate` skips the check. In the Node kit `null` is an unreadable source; in Python, where `None` is
|
|
85
|
+
the absent value, pass a function returning `None` for a source that has nothing.
|
|
86
|
+
- **What you pass in.** Pass only a revision that `verify_estate_record` accepted and whose number is not lower than
|
|
87
|
+
the highest the machine applied; a higher one only when you verified its chain from the applied revision.
|
|
88
|
+
- `hub_key_certified(record, kid, x, at, applied, extra_revocations)` /
|
|
89
|
+
`hubKeyCertified(record, kid, x, at, applied, {extraRevocations})` answers the question on its own:
|
|
90
|
+
`{certified: true, machine, revision}` or `{certified: false, fault, detail}`. Both kits are held to the contract's
|
|
91
|
+
`fixtures/vectors/hub-key.json` (`test/fixtures/contract/hub-key-vectors.json`).
|
|
75
92
|
|
|
76
93
|
## Checking a record you produce
|
|
77
94
|
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md
CHANGED
|
@@ -73,12 +73,52 @@ operator's real jobs.
|
|
|
73
73
|
## One copy under a supervisor
|
|
74
74
|
|
|
75
75
|
Started by hand, a copy that finds `service.lock` held prints one sentence naming the
|
|
76
|
-
holder
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
holder (or saying the holder is unknown, when its pid cannot be read) and exits 75 at once.
|
|
77
|
+
Under launchd that exit is a respawn every `ThrottleInterval` (10 s), forever, and under
|
|
78
|
+
systemd one every `RestartSec`. So a copy its supervisor started, and that says so with the
|
|
79
|
+
**supervisor marker**, backs off in-process — 0.5 s doubling to 30 s, 300 s in all — takes over
|
|
80
|
+
if the holder leaves, and exits 75 only when the wait runs out: a few starts an hour instead of
|
|
81
|
+
360. It touches nothing while it waits. Two installs sharing one data directory are still the
|
|
82
|
+
defect to remove; the back-off only stops the loop from costing the machine.
|
|
83
|
+
|
|
84
|
+
The marker (contract DEC-0057, CT-01) is the variable `FABRIC_SERVICE_SUPERVISOR=<manager>` or
|
|
85
|
+
the argument `--fabric-supervisor <manager>` (also `--fabric-supervisor=<manager>`), after the
|
|
86
|
+
executable and before any `--`; `<manager>` is `launchd`, `systemd`, `task-scheduler` or `node`
|
|
87
|
+
(Project Observatory's node, which supervises the services it starts). The writer of the
|
|
88
|
+
supervisor file writes it: `launchd_plist` and `systemd_unit` / `systemdUnit` set the variable,
|
|
89
|
+
always; `task_xml` / `taskXml` put the argument in the task's `Arguments`, because a task's action
|
|
90
|
+
carries no environment. Each writer refuses a marker already in the program arguments. A copy
|
|
91
|
+
never infers supervision from its parent process.
|
|
92
|
+
|
|
93
|
+
- `supervisor_marker(env, argv)` / `supervisorMarker({env, argv})` reads both forms and answers
|
|
94
|
+
`supervised` (with the manager), `manual` or `conflict`. Only an empty variable counts as no
|
|
95
|
+
marker; a bare `--fabric-supervisor` (followed by nothing, by `--` or by another `--…` flag,
|
|
96
|
+
none of which it consumes) names no manager.
|
|
97
|
+
- Markers that disagree — the variable and the argument with different values, the argument
|
|
98
|
+
twice, or a bare or empty argument beside the variable — exit 75 at once, before the lock and
|
|
99
|
+
whether or not it is held, naming both markers.
|
|
100
|
+
- A service's own argument parser accepts the flag in both spellings and ignores it: in Python,
|
|
101
|
+
`add_supervisor_argument(parser)` (argparse otherwise exits 2 on an unknown flag); the sample
|
|
102
|
+
service adds it to the parser and to each command.
|
|
103
|
+
- The kits are held to the contract's `fixtures/vectors/service-supervisor.json`, and their
|
|
104
|
+
starts against a held lock are recorded and read as `FAC-SEM-078` reads them
|
|
105
|
+
(`test/*supervisors*`).
|
|
106
|
+
|
|
107
|
+
## An app's own descriptor and the node
|
|
108
|
+
|
|
109
|
+
**An app** a person installs and starts has no supervisor (contract DEC-0069). Its descriptor says
|
|
110
|
+
`"lifecycle": {"manager": "person", "app": "<the installed bundle or executable>"}`, `installedBy {by: "person", role,
|
|
111
|
+
tool}` and no `origin`: it says the app is installed, never that it runs. The installer writes it; where there is none
|
|
112
|
+
(a macOS app dragged into Applications), the app writes it on a start when `app_descriptor_action` /
|
|
113
|
+
`appDescriptorAction` answers `write` — no descriptor yet, another version in `installedBy.tool`, or another path than
|
|
114
|
+
`lifecycle.app` — and never from an App Translocation path. Its live origin and process id go in its own liveness file,
|
|
115
|
+
rewritten on every start and removed on quit; a caller that sends anything reads that file and checks the process.
|
|
116
|
+
`write_descriptor` claims no port for it, and a reader that finds `lifecycle.app` gone treats the descriptor as stale.
|
|
117
|
+
|
|
118
|
+
**The node** (DEC-0061): `"lifecycle": {"manager": "node"}` is a local service Project Observatory's node installed and
|
|
119
|
+
supervises as its child. There is no supervisor file, so `label`, `plist`, `unit` and `task` are refused beside it; the
|
|
120
|
+
node starts the child with the supervisor marker (`--fabric-supervisor node` or the variable) and owns Install, Off,
|
|
121
|
+
Code, Uninstall and state.
|
|
82
122
|
|
|
83
123
|
## Directories
|
|
84
124
|
|
|
@@ -151,7 +191,7 @@ control character (a line break would end a unit line early).
|
|
|
151
191
|
**Linux — a systemd user unit.** `systemd_unit(unit, argv, working_directory=…)` /
|
|
152
192
|
`systemdUnit(unit, argv, {workingDirectory})` writes `Restart=always`, `RestartSec=10`,
|
|
153
193
|
`TimeoutStopSec` above the drain (15 s default), `KillMode=mixed`, `WantedBy=default.target`
|
|
154
|
-
and `FABRIC_SERVICE_SUPERVISOR=systemd
|
|
194
|
+
and `FABRIC_SERVICE_SUPERVISOR=systemd` (always, whatever the caller passed), quoting every argument (`%` → `%%`, `$` → `$$`).
|
|
155
195
|
`WorkingDirectory=` is written unquoted: systemd reads it as the rest of the line, so quotes would
|
|
156
196
|
become part of the path and the unit would be refused (`systemd-analyze verify` checks it in CI). The
|
|
157
197
|
descriptor says `"lifecycle": {"manager": "systemd", "unit": "<id>.<instance>.service"}`.
|
|
@@ -179,9 +219,12 @@ CI job has the real `schtasks` create, read back and delete such a task. The des
|
|
|
179
219
|
| stop, keep off | `schtasks /End /TN <task>`, then `schtasks /Change /TN <task> /DISABLE` |
|
|
180
220
|
| uninstall | `task_uninstall` / `taskUninstall`: `/End`, then `/Delete /F` |
|
|
181
221
|
|
|
182
|
-
A task cannot set an environment variable, so
|
|
183
|
-
|
|
184
|
-
|
|
222
|
+
A task cannot set an environment variable, so `task_xml` / `taskXml` write the marker as an
|
|
223
|
+
argument, `--fabric-supervisor task-scheduler`, before any `--`: a copy the Task Scheduler starts
|
|
224
|
+
backs off in-process like one under launchd or systemd. `IgnoreNew` keeps the scheduler from
|
|
225
|
+
starting a second copy. Measured (FAA-38, [report](https://github.com/passioncode-ai/fabric-agent-adapter/blob/main/docs/reports/2026-10-13-faa-38-windows-exit75/README.md)):
|
|
226
|
+
`RestartOnFailure` does not restart a task whose action exits 75, so that wait is the only recovery there; a copy
|
|
227
|
+
that still exits 75 stays down until the next logon.
|
|
185
228
|
|
|
186
229
|
**One copy on each system.** The Python kit takes `flock` (Linux) or a locked byte
|
|
187
230
|
(Windows); the Node kit cannot, so it holds a name instead — an abstract unix socket on Linux,
|
|
@@ -192,7 +235,14 @@ holds the same name beside its file lock, so the two kits exclude each other eve
|
|
|
192
235
|
|
|
193
236
|
**Token files on Windows.** The privacy of `service.token` is its ACL (service.md *Windows
|
|
194
237
|
token files*): the kits write a protected list — the user, SYSTEM and Administrators, set before the
|
|
195
|
-
token's bytes go in, any entry already there dropped
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
238
|
+
token's bytes go in, any entry already there dropped. On read, `windows_token_acl_problem` /
|
|
239
|
+
`windowsTokenAclProblem` decide over what was read — the file's flags, its owner SID and each ACE
|
|
240
|
+
as `{type, sid, mask}` (DEC-0057, `FAC-SEM-079`, held to the contract's
|
|
241
|
+
`fixtures/vectors/windows-token.json`): a reparse point, a directory, a real path outside the
|
|
242
|
+
profile, another owner, an allow ACE with a non-zero mask for any other SID, an ACE of another type
|
|
243
|
+
than allow or deny (an object or callback ACE), or anything unreadable is refused, naming the SID;
|
|
244
|
+
deny ACEs and an allow with an empty mask grant nothing; SIDs compare case-insensitively. The Python
|
|
245
|
+
kit reads the ACEs from the file's SDDL (`acl_from_sddl`); the Node kit reads them with Windows
|
|
246
|
+
PowerShell 5.1 (.NET's raw descriptor, no modules), converting .NET's signed mask to the unsigned
|
|
247
|
+
one, and writes the list with `icacls`; the path goes through the environment, never a command line.
|
|
248
|
+
An inherit-only ACE applies to children, not to the file, and is not read.
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# fabric-service/0.1 — wire reference
|
|
2
2
|
|
|
3
|
-
Pinned to `fabric-agent-contract` commit `
|
|
3
|
+
Pinned to `fabric-agent-contract` commit `fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0`
|
|
4
4
|
(`docs/specification/service.md`, DEC-0015; the usage report DEC-0021; Windows and Linux supervision
|
|
5
5
|
DEC-0032 and DEC-0033; the operator channel DEC-0034 and DEC-0035; the embedded dashboard DEC-0036;
|
|
6
6
|
delegation and the operator session's lifetime DEC-0039; the meter's call window DEC-0041; session handoff DEC-0042;
|
|
7
7
|
canonical forms DEC-0043; `degraded[].setup` and `run.mcp.stdio.scope` DEC-0044; delegation sealed and trust tiers
|
|
8
|
-
DEC-0045; runner and model profiles DEC-0046
|
|
8
|
+
DEC-0045; runner and model profiles DEC-0046; the estate record DEC-0055; one copy under any supervisor and its marker,
|
|
9
|
+
the Windows token-file reader DEC-0057; the hub key the estate certifies DEC-0060; signing credentials DEC-0063; the
|
|
10
|
+
estate record's 60 s skew DEC-0066). The contract's schemas are normative;
|
|
9
11
|
this page is the working summary. Extension key:
|
|
10
12
|
`https://fabric.passioncode.ai/agent-contract/extensions/service/0.1`.
|
|
11
13
|
|
|
@@ -29,13 +31,15 @@ File `<id>.<instance>.json`, mode 0600 (on Windows a protected ACL), atomic.
|
|
|
29
31
|
| `protocol` | `"fabric-service/0.1"` |
|
|
30
32
|
| `id`, `instance` | `^[a-z][a-z0-9-]{1,62}$`, `^[a-z][a-z0-9-]{0,31}$` (default `default`); the pair is unique per machine |
|
|
31
33
|
| `name`, `summary` | ≤ 80 and ≤ 200 characters |
|
|
32
|
-
| `origin` | `http://127.0.0.1:<port>`; the port is a machine-wide claim |
|
|
34
|
+
| `origin` | `http://127.0.0.1:<port>`; the port is a machine-wide claim. Optional for an app's descriptor only (`manager: "person"`, DEC-0069): its live origin and process id are in the app's own liveness file, and a descriptor without an origin claims no port (`FAC-SEM-010`) |
|
|
33
35
|
| `auth` | `tokenFile` (0600; on Windows a protected ACL — owner and every granting entry the current user, SYSTEM or Administrators: service.md *Windows token files*, DEC-0032, DEC-0033); `header` default `Authorization` with `scheme` `Bearer`; any other header carries the raw token with `scheme: "none"` |
|
|
34
|
-
| `lifecycle` | `manager` `launchd` (then `label` and `plist`), `systemd` (then `unit`, a `systemctl --user` unit), `task-scheduler` (then `task`, a per-user Scheduled Task) or `none`; `unit` and `task` are refused beside any other manager (DEC-0032)
|
|
36
|
+
| `lifecycle` | `manager` `launchd` (then `label` and `plist`), `systemd` (then `unit`, a `systemctl --user` unit), `task-scheduler` (then `task`, a per-user Scheduled Task), `node` (Project Observatory's node supervises it as its child, with no supervisor file, DEC-0061), `person` (an app a person installs and starts, then `app`, the installed app, DEC-0069) or `none`; `unit` and `task` are refused beside any other manager (DEC-0032), `label` and `plist` beside any but `launchd` (DEC-0057), `app` beside any but `person`. Both kits and the probe accept all six; both kits write the unit and the task (`systemd_unit`, `task_xml` / `systemdUnit`, `taskXml`). An app's descriptor says it is installed, never that it runs: the probe checks `app` is still there (`descriptor.app-installed`) and reads no well-known document; a reader that finds the app gone treats the descriptor as stale (`app_descriptor_stale` / `appDescriptorStale`), and an app that writes its own descriptor rewrites it only when `app_descriptor_action` / `appDescriptorAction` says `write` |
|
|
35
37
|
| `paths` | `data` (required), `logs[]`, optional `config`, `cache` |
|
|
36
38
|
| `commands` | only `doctor` and `update`, argument arrays whose first item is an absolute or `~/` path (Windows: also `C:\…`, `C:/…` or `~\…`, never a network share — DEC-0032) |
|
|
37
39
|
| `source.repository`, `fabricManifest` | optional |
|
|
38
|
-
| `installedAt`, `installedBy` | when and by
|
|
40
|
+
| `installedAt`, `installedBy` | when, and who installed it: the installer and its version as a string, or `{by, role?, tool}` (DEC-0061) — `by` `person` (with `role` `operator` or `administrator`), `node` or `organization`; `role` for a person only; `tool` the installer and its version. It names a role, never a person. An app's descriptor uses the object form with `by: "person"`. `register_remote` / `registerRemote` and the sample write the object form |
|
|
41
|
+
| `runner` | optional `{kind?, credentialKind}` (DEC-0061): `credentialKind` `subscription`, `key`, `platform-metered` or `none` — a kind, never a credential (`FAC-SEM-086` refuses a credential-shaped value here or in `installedBy`); a subscription names its vendor's runner (`claude-code`, `kimi-code`, `codex`) |
|
|
42
|
+
| `peer` | optional `{endpoint, custody, residency, maxDataClass}` (DEC-0061): the service is offered to others. `endpoint` `https://<dns-name>[:<port>][/<path>]`, never a reserved name (`FAC-SEM-085`); `custody` `organization-managed` or `personal`; `residency` 1–32 ISO 3166-1 alpha-2 codes, `EU` admitted, user-assigned codes refused; `maxDataClass` `public` < `internal` < `confidential` < `restricted`. An offer needs `runner` and never runs on a subscription. A host sends a request from anyone but the operator only when `peer_refusal` / `peerRefusal` answers null (`FAC-SEM-088`); usage rows may take `route: "platform-metered"` |
|
|
39
43
|
|
|
40
44
|
## Well-known document — `GET /.well-known/fabric-service`
|
|
41
45
|
|
|
@@ -172,8 +172,11 @@ class Probe:
|
|
|
172
172
|
self.add("descriptor.valid", "FAIL" if problems else "PASS", "; ".join(problems) or str(self.path))
|
|
173
173
|
if fs.WINDOWS:
|
|
174
174
|
# DEC-0032: on Windows the file's ACL is its privacy.
|
|
175
|
-
owner,
|
|
176
|
-
|
|
175
|
+
owner, aces = fs.windows_acl(self.path)
|
|
176
|
+
info = os.lstat(self.path)
|
|
177
|
+
problem = fs.windows_token_acl_problem({"isRegularFile": stat.S_ISREG(info.st_mode), "isReparsePoint": stat.S_ISLNK(info.st_mode),
|
|
178
|
+
"realPathInProfile": True, "ownerSid": owner, "aces": aces}, fs.windows_user_sid())
|
|
179
|
+
problem = problem and problem["message"]
|
|
177
180
|
self.add("descriptor.private", "FAIL" if problem else "PASS", problem or "owner-only ACL")
|
|
178
181
|
else:
|
|
179
182
|
mode = stat.S_IMODE(os.stat(self.path).st_mode)
|
|
@@ -186,10 +189,23 @@ class Probe:
|
|
|
186
189
|
continue
|
|
187
190
|
if key == me:
|
|
188
191
|
clashes.append("%s declared again in %s" % (me, path.name))
|
|
189
|
-
|
|
192
|
+
# DEC-0069: a descriptor without an origin (an app's) claims no port, so two of them never clash.
|
|
193
|
+
elif (not self.remote and "origin" in self.d and fs.placement_of(other) != "remote"
|
|
194
|
+
and other.get("origin") == self.d.get("origin")):
|
|
190
195
|
clashes.append("port %d also claimed by %s" % (self.port, key))
|
|
191
|
-
|
|
196
|
+
if self.remote:
|
|
197
|
+
unique = "a remote origin claims no port here"
|
|
198
|
+
elif "origin" not in self.d:
|
|
199
|
+
unique = "an app's descriptor names no origin, so it claims no port (FAC-SEM-010)"
|
|
200
|
+
else:
|
|
201
|
+
unique = "port %d is unique" % self.port
|
|
192
202
|
self.add("descriptor.port-claim", "FAIL" if clashes else "PASS", "; ".join(clashes) or unique)
|
|
203
|
+
if (self.d.get("lifecycle") or {}).get("manager") == "person":
|
|
204
|
+
# DEC-0069: installed, never running — a reader checks the app is still there; liveness is the app's own file.
|
|
205
|
+
present = fs.app_present(self.d)
|
|
206
|
+
self.add("descriptor.app-installed", "PASS" if present else "FAIL",
|
|
207
|
+
"%s is installed" % self.d["lifecycle"].get("app") if present
|
|
208
|
+
else "%s is gone: a reader treats this descriptor as stale" % self.d["lifecycle"].get("app"))
|
|
193
209
|
|
|
194
210
|
# well-known ----------------------------------------------------------------
|
|
195
211
|
def well_known_rules(self) -> None:
|
|
@@ -759,6 +775,10 @@ class Probe:
|
|
|
759
775
|
|
|
760
776
|
def run(self) -> List[Result]:
|
|
761
777
|
self.descriptor_rules()
|
|
778
|
+
if "origin" not in self.d and not self.remote:
|
|
779
|
+
self.add("well-known.answers", "NOT_RUN",
|
|
780
|
+
"an app's descriptor says it is installed, never that it runs; its live origin is in its own liveness file (DEC-0069)")
|
|
781
|
+
return self.results
|
|
762
782
|
if not self.port:
|
|
763
783
|
return self.results
|
|
764
784
|
self.well_known_rules()
|