@passioncode-ai/passioncode 0.2.58 → 0.2.59
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 +8 -0
- package/family.json +1 -1
- package/package.json +1 -1
- package/payload/.claude-plugin/marketplace.json +2 -2
- package/payload/manifest.json +8 -8
- package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +16 -2
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/source-and-instance.md +154 -0
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +995 -0
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_homes.py +72 -0
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_ignore.py +185 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +3 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +22 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +91 -1
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +14 -5
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md +154 -0
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""How fabric_source_check.py reads a home the owner's list declares (`home: <name>`, FAA-188 part 1): the name in
|
|
3
|
+
every form the table names, its parts joined by any separator a tool or a language writes, and its spellings for the
|
|
4
|
+
masks of printed paths. Beside fabric_source_check.py, which imports it. Standard library only.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from typing import Dict, Iterable, List, Set, Tuple
|
|
11
|
+
|
|
12
|
+
_SEP = r"(?:\\|/)+" # one way to read a run: two alternatives that both take a `\\` backtrack exponentially
|
|
13
|
+
|
|
14
|
+
#: How a `home: <name>` line of the owner's list is kept among the entries: a character no line can hold.
|
|
15
|
+
HOME_ENTRY = "\0"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _owner_homes(entries: List[str]) -> Set[str]:
|
|
19
|
+
"""The home names the owner's list declares (`home: <name>`), which are never neutral."""
|
|
20
|
+
return {e[1:] for e in entries if e.startswith(HOME_ENTRY)}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_DASHED: Dict[str, Tuple["re.Pattern[str]", ...]] = {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
#: Where a declared home name stands in each form the table names: the slashed, drive, URL-encoded and dashed homes.
|
|
27
|
+
_DECLARED_SLASHED = (r"(?<![A-Za-z0-9_.-])(?:/var)?/home/", r"(?<![A-Za-z0-9_.-])/Users/",
|
|
28
|
+
r"(?<![A-Za-z0-9_.-])(?:/mnt)?/[A-Za-z]/Users/",
|
|
29
|
+
r"(?<![A-Za-z0-9_.-])(?i:[A-Za-z]:%sUsers%s)" % (_SEP, _SEP),
|
|
30
|
+
r"%2[Ff](?:Users|home)%2[Ff]", r"(?i:%5CUsers%5C)")
|
|
31
|
+
_DECLARED_DASHED = (r"(?:^|[^A-Za-z0-9-])(?:-Users|-home|-var-home|(?:-mnt)?-[A-Za-z]-Users)-",
|
|
32
|
+
r"(?<![A-Za-z0-9])(?i:[A-Za-z]--Users-)")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
#: One separator between the parts of a declared home name, in any spelling a tool or a language writes: a space, `.`,
|
|
36
|
+
#: `_`, `-`, `+`, `'`, an escaped `\'` or `\u0027`, an HTML `'` or `'`, or a percent-encoding of one of them.
|
|
37
|
+
_NAME_JOIN = r"(?:[ ._+'-]|\\'|\\x27|\\u0027|�?39;|&#[xX]27;|'|%2[07BbDdEe]|%5[Ff])"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#: Where an unfound declared name starts a word: no letter or digit before it, or one of `_NAME_JOIN`'s escapes that ends
|
|
41
|
+
#: in a letter or digit (a percent-encoding, `\x27`, `\u0027`); the escapes ending in `;` or `'` already leave no letter
|
|
42
|
+
#: or digit before the name. Kept beside `_NAME_JOIN`, so a separator added to one is added to the other.
|
|
43
|
+
_NAME_START = r"(?:(?<![A-Za-z0-9])|(?<=%[0-9A-Fa-f]{2})|(?<=\\x27)|(?<=\\u0027))"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _name_parts(name: str) -> List[str]:
|
|
47
|
+
return [part for part in re.split(r"[ ._'-]+", name) if part]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _spellings(found: Iterable[str], owner: Iterable[str]) -> Set[str]:
|
|
51
|
+
"""The names found, and every spelling of a declared home one of them spells (its parts joined by each separator
|
|
52
|
+
a tool writes), so a printed path masks the other spellings of that home too."""
|
|
53
|
+
names = set(found)
|
|
54
|
+
for name in owner:
|
|
55
|
+
parts = _name_parts(name)
|
|
56
|
+
body = re.compile(r"(?i:%s)" % (_NAME_JOIN + "+").join(re.escape(part) for part in parts), re.ASCII)
|
|
57
|
+
if parts and any(body.fullmatch(n) for n in names):
|
|
58
|
+
names |= {name} | {sep.join(parts) for sep in (" ", ".", "_", "-", "+", "'", "%20", "%27")}
|
|
59
|
+
return names
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _dashed_home(name: str) -> Tuple["re.Pattern[str]", ...]:
|
|
63
|
+
"""A declared home name in every form, read whole, its parts joined by any run of the separators a tool or a
|
|
64
|
+
language writes (`_NAME_JOIN`: a space, `.`, `_`, `-`, `+`, `'` and their escapes and percent-encodings), since
|
|
65
|
+
the generic forms end a name at a dash or a space, so `-Users-j-doe-proj` would read `j`. It ends only where a
|
|
66
|
+
letter or digit would continue it: a `.` or `_` after it (an extension, a sentence's end) is read as the declared
|
|
67
|
+
home, failing closed."""
|
|
68
|
+
if name not in _DASHED:
|
|
69
|
+
body = (_NAME_JOIN + "+").join(re.escape(part) for part in _name_parts(name)) or re.escape(name)
|
|
70
|
+
whole = r"((?i:%s))(?![A-Za-z0-9])" % body
|
|
71
|
+
_DASHED[name] = tuple(re.compile(prefix + whole, re.ASCII) for prefix in _DECLARED_SLASHED + _DECLARED_DASHED)
|
|
72
|
+
return _DASHED[name]
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""How fabric_source_check.py reads `.fabric-source-ignore` and `--exclude` (FAA-188 part 1): a line is a path from the
|
|
3
|
+
tree's root, matched byte by byte as Git matches (`*`, `?` and `[...]` inside one folder, `**` as a whole segment
|
|
4
|
+
across folders), and a line Git would read otherwise is refused. Beside fabric_source_check.py, which imports it.
|
|
5
|
+
Standard library only.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import re
|
|
13
|
+
import warnings
|
|
14
|
+
from typing import Dict, Iterable, List, Optional
|
|
15
|
+
|
|
16
|
+
IGNORE_FILE = ".fabric-source-ignore"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CheckError(Exception):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_GLOBS: Dict[str, "re.Pattern[str]"] = {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _as_bytes(text: str) -> str:
|
|
27
|
+
"""A path or a line as Git compares it: its UTF-8 bytes, one character each. An unpaired surrogate (a Windows file
|
|
28
|
+
name may hold one) is written as Git for Windows writes it (WTF-8), never a crash."""
|
|
29
|
+
if os.name == "nt": # Windows names are UTF-16: every surrogate in one is a character of the name, as WTF-8
|
|
30
|
+
return text.encode("utf-8", "surrogatepass").decode("latin-1")
|
|
31
|
+
try: # POSIX names are bytes: a surrogate here is one Python escaped, and surrogateescape gives that byte back
|
|
32
|
+
return text.encode("utf-8", "surrogateescape").decode("latin-1")
|
|
33
|
+
except UnicodeEncodeError:
|
|
34
|
+
return text.encode("utf-8", "surrogatepass").decode("latin-1")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _name_bytes(name: str) -> str:
|
|
38
|
+
"""A file name as Git compares it, one byte each: on POSIX the bytes on disk, encoded back with the file system's
|
|
39
|
+
own codec (os.walk and `_git_files` decoded it with that codec, so a name in any locale comes back exact); on
|
|
40
|
+
Windows its UTF-8 (WTF-8) bytes, as `_as_bytes` writes them. The ignore file is read as UTF-8 and stays `_as_bytes`."""
|
|
41
|
+
if os.name == "nt":
|
|
42
|
+
return _as_bytes(name)
|
|
43
|
+
try:
|
|
44
|
+
return os.fsencode(name).decode("latin-1")
|
|
45
|
+
except UnicodeEncodeError: # not a name os.walk or Git gave (a surrogate outside the escapes): read as `_as_bytes`
|
|
46
|
+
return _as_bytes(name)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _glob(glob: str) -> "re.Pattern[str]":
|
|
50
|
+
"""A line of the ignore file read from the tree's root: `*`, `?` and `[...]` stay inside one folder, and `**` as a
|
|
51
|
+
whole segment crosses folders (`**/` also matches none); any other run of stars is one `*`. Not a `.gitignore`
|
|
52
|
+
line: a line with no `/` matches at the root only, and `\\` or `//` is refused (`_line`).
|
|
53
|
+
fnmatch's `*` would cross `/`, so `vectors/*.json` would leave out `vectors/private/live.json`. Raises re.error
|
|
54
|
+
for a class Python cannot read (`[9-0]`)."""
|
|
55
|
+
key = glob
|
|
56
|
+
if key not in _GLOBS:
|
|
57
|
+
glob = _as_bytes(glob) # Git matches bytes: `?` and a class read one byte of a UTF-8 name, never a letter
|
|
58
|
+
out, i = [], 0
|
|
59
|
+
while i < len(glob):
|
|
60
|
+
c = glob[i]
|
|
61
|
+
if c == "*":
|
|
62
|
+
end = i
|
|
63
|
+
while end < len(glob) and glob[end] == "*":
|
|
64
|
+
end += 1
|
|
65
|
+
whole = end - i >= 2 and (i == 0 or glob[i - 1] == "/") and (end == len(glob) or glob[end] == "/")
|
|
66
|
+
if whole and end < len(glob):
|
|
67
|
+
out.append("(?:.*/)?")
|
|
68
|
+
end += 1
|
|
69
|
+
else:
|
|
70
|
+
out.append(".*" if whole else "[^/]*")
|
|
71
|
+
i = end
|
|
72
|
+
continue
|
|
73
|
+
elif c == "?":
|
|
74
|
+
out.append("[^/]")
|
|
75
|
+
elif c == "[":
|
|
76
|
+
j = i + 1 + (glob[i + 1:i + 2] in ("!", "^"))
|
|
77
|
+
j += glob[j:j + 1] == "]"
|
|
78
|
+
end = glob.find("]", j)
|
|
79
|
+
if end < 0 or "[:" in glob[i + 1:end + 1]:
|
|
80
|
+
# Git matches nothing for an unclosed `[` and reads `[:digit:]` as a POSIX class: refused
|
|
81
|
+
raise re.error("a class Git reads otherwise")
|
|
82
|
+
else:
|
|
83
|
+
inner = glob[i + 1:end].replace("\\", "\\\\")
|
|
84
|
+
negated = inner[:1] in ("!", "^") # Git reads both as a negated class
|
|
85
|
+
body = inner[negated:]
|
|
86
|
+
if body[:1] in ("^", "["): # a literal right after the negation
|
|
87
|
+
body = "\\" + body
|
|
88
|
+
for mark in "&~|": # literals, never the set operations a later Python may read there
|
|
89
|
+
body = body.replace(mark, "\\" + mark)
|
|
90
|
+
out.append("(?!/)[%s%s]" % ("^" if negated else "", body))
|
|
91
|
+
i = end
|
|
92
|
+
else:
|
|
93
|
+
out.append(re.escape(c))
|
|
94
|
+
i += 1
|
|
95
|
+
with warnings.catch_warnings(): # a FutureWarning (`--` in a class) would print the script's own path
|
|
96
|
+
warnings.simplefilter("error")
|
|
97
|
+
try:
|
|
98
|
+
_GLOBS[key] = re.compile("(?s:%s)\\Z" % "".join(out))
|
|
99
|
+
except Warning:
|
|
100
|
+
raise re.error("a class Python may read as a set operation")
|
|
101
|
+
return _GLOBS[key]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _readable_glob(glob: str) -> bool:
|
|
105
|
+
"""Whether every form `_ignored` tries can be read: a reversed range (`[9-0]`) is refused on every Python."""
|
|
106
|
+
try:
|
|
107
|
+
for form in (glob, glob.rstrip("/")):
|
|
108
|
+
_glob(form) # every form, whether `_ignored` tries it for this line or not
|
|
109
|
+
except re.error:
|
|
110
|
+
return False
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _line(glob: str, where: str) -> str:
|
|
115
|
+
"""An ignore line as it is matched: a leading `/` is the root (it changes nothing); a `!` exception is refused, since a
|
|
116
|
+
file it would bring back is left out here (it fails open), and so is a glob that cannot be read."""
|
|
117
|
+
if glob.startswith("!"):
|
|
118
|
+
raise CheckError("%s is an exception (`!`), which the check does not read: name the files to leave out" % where)
|
|
119
|
+
# `//` and `\` mean something else to Git (no match, an escape): refused, never read as a wider line
|
|
120
|
+
if "//" in glob or "\\" in glob:
|
|
121
|
+
raise CheckError("%s holds `\\` or `//`: write the path from the root with single `/`" % where)
|
|
122
|
+
glob = glob[1:] if glob.startswith("/") else glob
|
|
123
|
+
if not glob or not _readable_glob(glob):
|
|
124
|
+
raise CheckError("%s is not a glob it can read" % where)
|
|
125
|
+
return glob
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def ignore_globs(root: Path, extra: Iterable[str]) -> List[str]:
|
|
129
|
+
globs = [_line(g.rstrip(" "), "an --exclude glob") for g in extra]
|
|
130
|
+
ignore = root / IGNORE_FILE
|
|
131
|
+
try:
|
|
132
|
+
present = ignore.is_file()
|
|
133
|
+
except OSError:
|
|
134
|
+
raise CheckError("the folder cannot be read")
|
|
135
|
+
if present:
|
|
136
|
+
try:
|
|
137
|
+
data = ignore.read_bytes().decode("utf-8-sig")
|
|
138
|
+
except (OSError, UnicodeDecodeError):
|
|
139
|
+
raise CheckError("%s cannot be read as UTF-8" % IGNORE_FILE)
|
|
140
|
+
# as Git splits it: at LF, a trailing CR dropped, trailing spaces trimmed; a tab or a leading space is the line's
|
|
141
|
+
for number, line in enumerate(data.split("\n"), 1):
|
|
142
|
+
glob = (line[:-1] if line.endswith("\r") else line).rstrip(" ")
|
|
143
|
+
if glob and not glob.startswith("#"):
|
|
144
|
+
globs.append(_line(glob, "line %d of %s" % (number, IGNORE_FILE)))
|
|
145
|
+
return globs
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _takes_folder(glob: str) -> bool:
|
|
149
|
+
"""A line that names a folder takes all it holds: one ending in `/`, or a path with no wildcard (`vendor/lib`). A
|
|
150
|
+
wildcard line takes only what it matches: `vectors/*` the files in `vectors`, never `vectors/live/config.json`."""
|
|
151
|
+
return glob.endswith("/") or not any(c in glob for c in "*?[")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _under(path: str, glob: str) -> bool:
|
|
155
|
+
"""Whether a folder above the path matches the line, read as a folder: `src/**/` takes `src/pkg/x`, never
|
|
156
|
+
`src/main.py` (joining the line with `/**` would read `**/` as no folder at all)."""
|
|
157
|
+
folder = _glob(glob.rstrip("/"))
|
|
158
|
+
return any(folder.match(path[:i]) for i, c in enumerate(path) if c == "/")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _ignored(path: str, globs: List[str], root: Optional[Path] = None) -> bool:
|
|
162
|
+
"""Whether a line leaves the path out. A folder (a nested repository, which Git lists as `vendor/sub/`, or a
|
|
163
|
+
submodule, listed as `vendor/sub`) is left out only by a line that names it as a folder (`vendor/sub/`,
|
|
164
|
+
`vendor/sub`, or a wildcard folder line such as `vendor/*/`, which takes every one it reaches): a wildcard line
|
|
165
|
+
without the `/` takes files, so `vendor/*` reports a nested repository until it is checked in its own tree and
|
|
166
|
+
named. A line ending in `/` never takes a file of that name."""
|
|
167
|
+
on_disk = path.rstrip("/")
|
|
168
|
+
name = _name_bytes(on_disk) # matched as Git matches: byte by byte, the bytes on disk
|
|
169
|
+
known: List[bool] = []
|
|
170
|
+
|
|
171
|
+
def folder() -> bool:
|
|
172
|
+
if not known:
|
|
173
|
+
known.append(path.endswith("/") or (root is not None and os.path.isdir(str(root / on_disk))
|
|
174
|
+
and not os.path.islink(str(root / on_disk))))
|
|
175
|
+
return known[0]
|
|
176
|
+
|
|
177
|
+
for g in globs:
|
|
178
|
+
if _takes_folder(g):
|
|
179
|
+
if _under(name, g):
|
|
180
|
+
return True
|
|
181
|
+
if _glob(g.rstrip("/")).match(name) and (not g.endswith("/") or folder()):
|
|
182
|
+
return True
|
|
183
|
+
elif _glob(g).match(name) and not folder():
|
|
184
|
+
return True
|
|
185
|
+
return False
|
|
@@ -14,7 +14,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
14
14
|
compatibility: Python 3.9+ or Node.js 20+ for the kits (Node.js 22+ for a Linux service's instance lock); 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.
|
|
15
15
|
metadata:
|
|
16
16
|
author: PassionCode.ai
|
|
17
|
-
version: "0.
|
|
17
|
+
version: "0.58.0"
|
|
18
18
|
contract-version: "0.1.0"
|
|
19
19
|
extension: "fabric-service/0.1"
|
|
20
20
|
extension-commit: "3830a3353923e20f826d466610e1c479f120bec0"
|
|
@@ -321,7 +321,8 @@ the well-known document must answer again with a new pid within about 15 seconds
|
|
|
321
321
|
|
|
322
322
|
To hand an agent to another machine or an organization, pack it as one folder (contract DEC-0050, DEC-0051). Write the
|
|
323
323
|
author's `fabric.package.json` (the descriptive fields; for an agent `resultSignal` and `evals` with the receipt of the
|
|
324
|
-
suite as packed),
|
|
324
|
+
suite as packed), run `fabric_source_check.py check <folder> --every-file` (adapting-projects-to-fabric, Python) on the clean copy
|
|
325
|
+
the package reference describes, then:
|
|
325
326
|
|
|
326
327
|
```bash
|
|
327
328
|
python3 <skill-dir>/scripts/fabric_package.py pack <folder> # or: node fabric-package.mjs, npx @passioncode-ai/fabric-agent-adapter pack
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md
CHANGED
|
@@ -42,6 +42,28 @@ python3 scripts/fabric_package.py pack <folder> # a dry run: the packa
|
|
|
42
42
|
python3 scripts/fabric_package.py pack <folder> --apply # writes fabric.package.json when nothing is wrong
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
Run `adapting-projects-to-fabric/scripts/fabric_source_check.py check <folder> --every-file` on the folder you pack first (a floor under your own
|
|
46
|
+
review, not a secret scanner): a package carries
|
|
47
|
+
the source, and the instance (the owner's paths, keys, strings) stays out of it (`references/source-and-instance.md` of
|
|
48
|
+
that skill). `--every-file` reads what `pack` reads, files Git ignores included; without it the check reads only what Git
|
|
49
|
+
would commit. A path named in `.fabric-source-ignore` or `--exclude` is still carried by `pack`, unread, so name only synthetic files
|
|
50
|
+
there, and check a nested repository named there with `--every-file` in its own tree. Keep the signing key file
|
|
51
|
+
outside the folder. Pack a clean copy: what Git would commit (`git ls-files --cached --others --exclude-standard`) plus the nested
|
|
52
|
+
trees checked on their own, copied without their `.git` and with each committed symbolic link replaced or removed by where its resolved
|
|
53
|
+
target is (`pack` refuses a link): a file or folder Git would commit in the tree being packed, or in a nested tree checked
|
|
54
|
+
on its own, is copied in its place (a folder's committed files, each link inside it by the same rule; a file the ignore
|
|
55
|
+
file names needs an ignore line of its own at the new path); a link whose target is a folder already being copied on
|
|
56
|
+
the way to it (an ancestor, or a folder a link above it led into) is removed, so the copy ends; a target that leaves
|
|
57
|
+
that tree or lies in what `.gitignore` keeps out, such as an instance folder in the checkout, is removed, since it is
|
|
58
|
+
the instance or another project. Source the agent shares from outside the tree (a monorepo's common code, linked or
|
|
59
|
+
imported by a relative path) is copied into the agent's tree at the path it is reached by and committed before
|
|
60
|
+
packing. List each link removed and review it: a missing file is not seen by `pack` or `verify`. Leave out dependency
|
|
61
|
+
folders (`node_modules`, virtual environments, which hold install paths and binaries), what `.gitignore` keeps out as
|
|
62
|
+
instance (the real configuration, an instance folder in the checkout) and the files `pack` refuses by name (a
|
|
63
|
+
credential-free `.npmrc` that `.fabric-source-ignore` names for the check included). `pack` does not read
|
|
64
|
+
`.gitignore`, and an account id or a host is found only through the owner's list. The check is Python only (3.9+); a Node-only machine packs without it, and `pack` itself carries the kit's token files
|
|
65
|
+
and a private `*.jwk` by their names: keep them in the instance.
|
|
66
|
+
|
|
45
67
|
It refuses to list what must not ship, and names each: a symlink, a special file, `.git`, `__pycache__`, `.DS_Store`,
|
|
46
68
|
`*.pyc`, and files that are credentials by name (`.env` and `.env.*` but `.env.example|sample|template`, `*.pem`, `*.key`,
|
|
47
69
|
`*.p12`, `*.pfx`, `*.jks`, `*.keystore`, `id_rsa`-style keys, `.npmrc`, `.netrc`, `.pypirc`). Remove them or pack a clean
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabric-service-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"about": "The service kit's modules in dependency order: a module imports only modules listed before it. Written by fabric_kit.py build; edit the order and layers by hand, never the digests.",
|
|
5
5
|
"python": [
|
|
6
6
|
{
|
|
@@ -4318,6 +4318,96 @@
|
|
|
4318
4318
|
"fabric-openrouter.mjs": "6041f3ab30f516658e1dbab4897a651cc8509b2136cfa8e769802c6b40ec2f35",
|
|
4319
4319
|
"fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
|
|
4320
4320
|
"share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
|
|
4321
|
+
},
|
|
4322
|
+
"0.58.0": {
|
|
4323
|
+
"fabric_jsvalue.py": "12888a2c934a885d35999ac9782eaedc8d04946d9775213344a235a8814d3d28",
|
|
4324
|
+
"fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
|
|
4325
|
+
"fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
|
|
4326
|
+
"fabric_secrets.py": "1d0191f0ec108fdc68345056b8c574b1f691509d1c353e41d98bfd58a59e8d3c",
|
|
4327
|
+
"fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
|
|
4328
|
+
"fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
|
|
4329
|
+
"fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
|
|
4330
|
+
"fabric_paths.py": "8a1ee742f80f436e9f28c61b4f049a9545de2a9e0d66bfc61faa6ee5d1ba3734",
|
|
4331
|
+
"fabric_estate_rules.py": "8b36a7f4108d9c2b1fb38c8bff344778c807bed631a99b4a563ecb0a49c10b1c",
|
|
4332
|
+
"fabric_estate.py": "b3cd56dcb86e04d252980322ac8054da1aaef24ac64690f7aa80e7c765ee6f60",
|
|
4333
|
+
"fabric_mandate.py": "050c43d0166a7dfba82e29c4e978f05c364cf6da06d0a781bd023a566777c17b",
|
|
4334
|
+
"fabric_delegation.py": "716f0ff7ac01095f54b24970beac1962128e8ff33026b243fc0467876142ab03",
|
|
4335
|
+
"fabric_share.py": "da36a466426fc29dd7dd40013167f3959a18375f8fedd228bf64f298cec1040c",
|
|
4336
|
+
"fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
|
|
4337
|
+
"fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
|
|
4338
|
+
"fabric_package.py": "ff533f1b3f92b4ea0f532fcec50b68c610d47549930eef8ad5247692a64dfb3c",
|
|
4339
|
+
"fabric_protocols.py": "42529a7df4d753544d2e37a18ec81266d6e1e80477cb8558a3ab521416d45791",
|
|
4340
|
+
"fabric_actions.py": "ae21d1199614c4f74d1dcd1e0a87107a9d52434cd9e2d64aa24a7b1e196fb723",
|
|
4341
|
+
"fabric_connections.py": "7de55cb7d808f756d650effabfaa12027d40a5bd5d2f6d448c32969d47529ecb",
|
|
4342
|
+
"fabric_a2a.py": "8b01502131215bb4de1c01866187c56e99b791543923ed8577535573db33b919",
|
|
4343
|
+
"fabric_page_links.py": "1b57ea573c550e2f90d1ec3edcc655917ceaa0db9dd1635fccf422ec3024d25e",
|
|
4344
|
+
"fabric_lock.py": "ba2399d2137c7a7791b7133b9db9cca36693f4eb7e30955b25a75ade814e95ba",
|
|
4345
|
+
"fabric_usage.py": "5cc2e03ccc5b06e81987ed7bc3eead7a7e6cbf1a7cb8e05c6c64315eed9d91b6",
|
|
4346
|
+
"fabric_backups.py": "1f3f0d7ecf361b29e9101e07f1c53e5dc304d598bf26fd59ec89f16b9fed2420",
|
|
4347
|
+
"fabric_descriptor.py": "a6d7a4a38186f7ef527d16218bbf96e39e255f9547be668adcfbec063dabb7ed",
|
|
4348
|
+
"fabric_context.py": "6ae311ddc7217a4aff915e7ce2646486931acd128462b7bebe4a52bbb5fbb6c7",
|
|
4349
|
+
"fabric_auth.py": "71115d18c67b7b806cb57b64b77e01ad97fc0e63e6f49ee850f778123617d905",
|
|
4350
|
+
"fabric_events.py": "cd94b186e2a8f8cb4ad2b6bd44d83ec5021933075e28c5d655f387f16dff42b8",
|
|
4351
|
+
"fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
|
|
4352
|
+
"fabric_supervisors.py": "3c5b2e8c310738720b1c1b779be4710e90251013d9be4c68f9952edc0bf55d5f",
|
|
4353
|
+
"fabric_service.py": "a2e813469a47d4d3eea996628635c8903007498bdb6a1ae032a7a5ce6f49fe2f",
|
|
4354
|
+
"fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
|
|
4355
|
+
"fabric_interop_rules.py": "82c079a4d9421ea2cd10c1dfa8e1c447b80d5e0dcc8d52d52415bf028e34c91e",
|
|
4356
|
+
"fabric_interop.py": "8f7e43bf01fa5e969d7d819f4b0188b9acfd846f6f85a37ea139e550d61e4e6c",
|
|
4357
|
+
"fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
|
|
4358
|
+
"fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
|
|
4359
|
+
"fabric_board_link.py": "e8d2310e5f53c75e4bf642c3d980b2faba692731eaf56ec245cea05446e38bef",
|
|
4360
|
+
"fabric_board_slots.py": "a3c705cded822103703a6c2d734bcd505fcd1d6c0293150c77dfa266fd416d53",
|
|
4361
|
+
"fabric_board_session.py": "9032c04fbafb63fd93809b494518aaa039215abef973ad181d960350beb56a9f",
|
|
4362
|
+
"fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
|
|
4363
|
+
"fabric_board.py": "34c1200860040aa61cdd35f911f70d34d64b4a5cd91f892328d1169e4a89d8cd",
|
|
4364
|
+
"fabric_http.py": "80e22ae0bba91de13a94d64b595108b39c58f16f7b4137f3a48bdd9503f424fe",
|
|
4365
|
+
"fabric_openrouter.py": "d06a40f83f2fb8bd441ab3485ae882f1fbdd9cd7a9c1ec175eb47cb8ae2cce81",
|
|
4366
|
+
"fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
|
|
4367
|
+
"fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
|
|
4368
|
+
"fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
|
|
4369
|
+
"fabric-secrets.mjs": "6ad8ab18ac899ecc2384e8ec6a3fb689b92be3b9a5ce3a59b92a37d52da5d766",
|
|
4370
|
+
"fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
|
|
4371
|
+
"fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
|
|
4372
|
+
"fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
|
|
4373
|
+
"fabric-paths.mjs": "aa630d611e748a899e7043480f4a4e46b444f2e15ff945f30c2fbef21c1ceea5",
|
|
4374
|
+
"fabric-estate-rules.mjs": "7476be71174aa5b1026134f2b335abf647feaa55584e2f9de8618e3054aa8e1e",
|
|
4375
|
+
"fabric-estate.mjs": "e8a2e62d2313121e6cb502f6e6abad94d1d546a44b4d9b3ce2be29e7808e8cf3",
|
|
4376
|
+
"fabric-mandate.mjs": "deaab2bfb929d1bd35ae1bad71e7b9ad9902cc23b8eb51819ed592fd80eddf95",
|
|
4377
|
+
"fabric-delegation.mjs": "04eaf6cd1786d4309a253a27dbdb05e27b408cb511aa41b1633a96634b775e65",
|
|
4378
|
+
"fabric-share.mjs": "bfb26581c21ad984c2a337da02429f9f3a165cc582f8ba48de4d3dfb7d163a59",
|
|
4379
|
+
"fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
|
|
4380
|
+
"fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
|
|
4381
|
+
"fabric-package.mjs": "13913ea6d12056c3a31b8222b6c4aab8395941fe519913692e1a06d8672e1c88",
|
|
4382
|
+
"fabric-protocols.mjs": "5fd8765fdd3e507cc85c108492682bf5cbb4ae08873245c50374fb01d33c75e4",
|
|
4383
|
+
"fabric-actions.mjs": "06e42f5e24c96311d5417d0d1effa93479ef7128a7ab139903b2bcf59253f6be",
|
|
4384
|
+
"fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
|
|
4385
|
+
"fabric-a2a.mjs": "51b72fe1d80649d659fcad3a8f96370db3476c4a7b30cbabd8522c138be69346",
|
|
4386
|
+
"fabric-page-links.mjs": "a8834926e9d4bbf7c4857f551451d2ebf6ac8873bbccaaf89c5e30cf45b17ee6",
|
|
4387
|
+
"fabric-lock.mjs": "ac219ab0a326d5c43c7c4c255e0c3843d4794de795a60e3af661e7d5350ea052",
|
|
4388
|
+
"fabric-usage.mjs": "1c4d18004639409624ac3516a02f2b60bbf55abdb612e9a96c37e83666eb87a7",
|
|
4389
|
+
"fabric-backups.mjs": "cfd8c9085e3d597a783099c2bd2ffe2f3faa0daaadab46dedd30523894cfeb06",
|
|
4390
|
+
"fabric-descriptor.mjs": "243bbce42989f67c4c5ec6b7659e201b3f9a1c08c71d429d88ec934d870f5104",
|
|
4391
|
+
"fabric-context.mjs": "1112d695e872f9825cbc94f08484974bff3880087bc29b8be59f885f03e8f7be",
|
|
4392
|
+
"fabric-auth.mjs": "782c2aa732f8320f604194660117640a7ea145e12b8155c04e88cef7c516a5b3",
|
|
4393
|
+
"fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
|
|
4394
|
+
"fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
|
|
4395
|
+
"fabric-supervisors.mjs": "d45c38de0cb3ed85e083496c2d3c51dab004454accdd87f4762a512a6d442573",
|
|
4396
|
+
"fabric-service.mjs": "6aa673657427211b491a9b63f94b9847a7e6aa3ee1b7353e1af11f46469803a3",
|
|
4397
|
+
"fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
|
|
4398
|
+
"fabric-interop-rules.mjs": "e4f8b133138e27353a7976c0360bd1653c90daed43b3f843f85a19f14f5e9221",
|
|
4399
|
+
"fabric-interop.mjs": "ee7e6d32be1f42e6d21a943b8886652352408c49c6404d9749b8a02dc3901e92",
|
|
4400
|
+
"fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
|
|
4401
|
+
"fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
|
|
4402
|
+
"fabric-board-link.mjs": "b1ac329b65b679193b94b81fe735922e14427ed540ba910b1a815ce3447e3220",
|
|
4403
|
+
"fabric-board-slots.mjs": "43eb7ceb6c62989a4eb4b2089d1808e2c3cf8f873fcf9216fcb4c285e5b39eb4",
|
|
4404
|
+
"fabric-board-session.mjs": "04aa920b24a379157c55ebe3de516eb8b46bd0b0db50e4b883528d4ede7ed978",
|
|
4405
|
+
"fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
|
|
4406
|
+
"fabric-board.mjs": "0d75c3e27a0b791bdbc35b2e93b3299e5831d3ab3003ce441e6268397e32b448",
|
|
4407
|
+
"fabric-http.mjs": "9f899829073470b9c2b449605a9371b773848eec4e3a0122ed4ccde5eddcdb4b",
|
|
4408
|
+
"fabric-openrouter.mjs": "6041f3ab30f516658e1dbab4897a651cc8509b2136cfa8e769802c6b40ec2f35",
|
|
4409
|
+
"fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
|
|
4410
|
+
"share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
|
|
4321
4411
|
}
|
|
4322
4412
|
},
|
|
4323
4413
|
"data": {
|
|
@@ -5,7 +5,7 @@ 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. Ships in one plugin with adapting-projects-to-fabric, whose scripts it reuses.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.58.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "3830a3353923e20f826d466610e1c479f120bec0"
|
|
11
11
|
---
|
|
@@ -73,8 +73,10 @@ failures, a retro), distil it before writing code:
|
|
|
73
73
|
failures and dead ends).
|
|
74
74
|
3. **Convert every trap into a planted eval fixture.** The new agent is not done until
|
|
75
75
|
it has been watched rejecting the exact defects its predecessors were burned by:
|
|
76
|
-
knowledge transfers as a check, not as prose.
|
|
77
|
-
|
|
76
|
+
knowledge transfers as a check, not as prose. A fixture carries the defect on neutral
|
|
77
|
+
ids, never the predecessor's owner, paths or accounts.
|
|
78
|
+
4. Record the pack in the new project as `docs/knowledge-pack.md` with its sources (a public URL or commit, else a
|
|
79
|
+
neutral name, the private address kept in the instance), so
|
|
78
80
|
the provenance of every borrowed decision survives the person who borrowed it.
|
|
79
81
|
|
|
80
82
|
Skipping this step because there is no prior art is legal; skipping it because reading
|
|
@@ -122,6 +124,13 @@ at start. Either way: no `.env` of its own, no key file beside its data, no valu
|
|
|
122
124
|
configuration or a commit. A workflow names the keys it needs in its checkpoint (`credentials`),
|
|
123
125
|
never their values.
|
|
124
126
|
|
|
127
|
+
**The source stays apart from the instance**, from the first commit: instance values (the
|
|
128
|
+
repository address of the owner's account, folders, the owner, accounts, hosts) live in a configuration outside the repository, read through a
|
|
129
|
+
named variable with a neutral default and a committed `*.example`; records, logs and state documents live in the
|
|
130
|
+
instance folder; fixtures use neutral ids. Load [the source and the instance](references/source-and-instance.md). The
|
|
131
|
+
sibling's `scripts/fabric_source_check.py check <project-root>` finds instance data in the tree without repeating it;
|
|
132
|
+
run it with the owner's list of their own strings, or record that none was given (the owner's own home name, when it is neutral, has one or two letters, or holds a space, `.`, `_`, `-` or `'`, is declared there with `home: <name>` first; a container's, a cloud image's or a CI runner's user that only the image uses, such as `node` or `vscode`, is never declared); the skeleton is done when it exits 0 and the owner has reviewed it (a floor, not a secret scanner; without the sibling or Python 3.9+, record the check as NOT_RUN, as in step 3). Handing the source over stays the owner's act.
|
|
133
|
+
|
|
125
134
|
## Step 3: scaffold the provider bundle
|
|
126
135
|
|
|
127
136
|
Reuse the sibling skill's scaffolder against the new skeleton with the profile chosen at
|
|
@@ -181,8 +190,8 @@ lets the output decide what counts as success.
|
|
|
181
190
|
|
|
182
191
|
Run the sibling skill's `check` (structural, then exact when the contract checkout is
|
|
183
192
|
available) and complete `fabric/FABRIC-CONFORMANCE.md` with dated receipts, exactly as
|
|
184
|
-
its workflow steps 5–7 prescribe; this skill adds no
|
|
185
|
-
purpose. Then record one expectation the report must carry:
|
|
193
|
+
its workflow steps 5–7 prescribe, and run its `fabric_source_check.py check` again (exit 0); this skill adds no
|
|
194
|
+
second verification path on purpose. Then record one expectation the report must carry:
|
|
186
195
|
|
|
187
196
|
> When a Fabric host admits this provider, it enters under a **canary binding** (a
|
|
188
197
|
> checker on its output and a budget cap), regardless of who wrote it. Unsupervised
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# The source and the instance
|
|
2
|
+
|
|
3
|
+
An agent is two things. Its **source** is what can be handed to someone else: code, skills, prompts, schemas,
|
|
4
|
+
examples, the source documentation. Its **instance** is what belongs to one installation: records, logs, journals,
|
|
5
|
+
keys, the owner's names and paths, accounts, hosts and its configuration. A new agent keeps the two apart from its
|
|
6
|
+
first commit, so that its owner can hand the source over later without rewriting its history. Nothing is handed over
|
|
7
|
+
by itself: handing over, a new remote, a push or a change of visibility stays the owner's act, each time (a personal
|
|
8
|
+
agent stays private until its owner says so about that act).
|
|
9
|
+
|
|
10
|
+
The format of a source manifest, export and import is the contract's to decide (CT-194). What follows does not depend
|
|
11
|
+
on it.
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- [The layout](#the-layout)
|
|
16
|
+
- [The check](#the-check)
|
|
17
|
+
|
|
18
|
+
## The layout
|
|
19
|
+
|
|
20
|
+
| Where | What | In Git |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| the repository | code, skills, prompts, schemas, the source documentation, fixtures on neutral ids (`example-agent`, `/Users/example`) | yes |
|
|
23
|
+
| `config/<name>.example.json` (or `.env.example`, `*.example.yaml`) | every instance value the agent reads, with a neutral default: the repository address of the owner's account, folders, the owner, account ids, hosts | yes, the example only |
|
|
24
|
+
| the instance folder, outside the repository | the real configuration, records (a database included), logs, journals, state documents | never in the source's repository (a separate private repository of the owner's data belongs to the instance) |
|
|
25
|
+
| the secret store | key values; the source names each key by name only (`EXAMPLE_API_KEY`) | never |
|
|
26
|
+
|
|
27
|
+
- **The instance folder is the agent's data folder.** An agent that is not a kit service reads it through one named
|
|
28
|
+
variable (`<AGENT>_DATA_DIR`) that overrides its default. A service built with `building-fabric-services` takes the
|
|
29
|
+
kit's folders unchanged (the Directories table in its `references/lifecycle.md`, `fabric_paths.service_dirs`): on macOS `~/Library/Application Support/<id>/`,
|
|
30
|
+
on Linux `${XDG_DATA_HOME:-~/.local/share}/<id>/`, on Windows `%LOCALAPPDATA%\<id>\`. An agent's second context
|
|
31
|
+
(an organization's) is a sibling instance, `<id>.<instance>`, never a folder inside the personal one
|
|
32
|
+
(`references/estate-record.md` of that skill).
|
|
33
|
+
- **Ignore the instance in the source**: `.gitignore` names the real configuration (`config/*.json` with
|
|
34
|
+
`!config/*.example.json`), an instance folder that sits inside the checkout during development, and what running
|
|
35
|
+
the agent leaves (`__pycache__/`, `*.pyc`), which the check would otherwise report as binary in Git mode.
|
|
36
|
+
- **State documents are instance data.** A handoff, a decision log or a backlog about this codebase is source
|
|
37
|
+
documentation; a record of what this installation did (runs, conversations, the owner's notes, a journal) lives in
|
|
38
|
+
the instance folder.
|
|
39
|
+
- **Keys by name.** The README's Configure section lists key names, the agent reads each from its environment or the
|
|
40
|
+
secret store, and no value is ever written to a file, a fixture, an argument or a commit.
|
|
41
|
+
- **Fixtures on neutral ids.** A test that needs a home folder, an owner or an account uses `example`, `example-agent`,
|
|
42
|
+
`owner@example.com` or a one-letter home, and a credential-shaped test value says it is synthetic
|
|
43
|
+
(`sk-test-not-a-key`) or is assembled at run time.
|
|
44
|
+
- **Two names stay in the source until the contract decides (CT-194).** A service's supervisor label is the agent's
|
|
45
|
+
own name, reverse-DNS on the publisher's domain (`com.example.example-agent`), and `AGENTS.md` names it (LC-09):
|
|
46
|
+
never the owner's account or name. A provider's `createdBy` names the publisher; until one is chosen it
|
|
47
|
+
keeps the scaffold's sentinel, and a person's name or account is never written there. The adapter moves both when the
|
|
48
|
+
contract decides how an instance renames them (FAA-188 part 2).
|
|
49
|
+
|
|
50
|
+
## The check
|
|
51
|
+
|
|
52
|
+
`fabric_source_check.py check` reads the files the source would carry (what Git would commit: tracked files and
|
|
53
|
+
untracked ones that are not ignored, as they are in the work tree; a staged copy that differs is not read) and reports each place where the instance shows through, by path, line and kind:
|
|
54
|
+
|
|
55
|
+
| Kind | Finds |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `home-path` | an absolute path inside a person's home folder (a web route under `/home/` is reported too; a route under `/users/` is not): `/Users/<name>/`, `/home/<name>/`, `C:\Users\<name>\`, Git Bash's `/c/Users/<name>/`, WSL's `/mnt/c/Users/<name>/`, and the dashed (`-Users-<name>-`, `C--Users-<name>-`) and URL-encoded forms a tool makes of one. The name is ASCII, and in the dashed form it ends at the next dash: an owner whose home name holds a space, a dash, a dot, an underscore or an apostrophe (each a dash in the dashed form; a slashed name also ends at a space) declares it with `home: <name>` in the owner's list, which reads it whole in every form, and one whose home name is not ASCII, or holds another character, lists it as a plain string (a typographic apostrophe a keyboard typed, which the folder's name does not hold, is written as `'` in `home:`; a folder whose name holds `’` is listed as a plain string with `’` kept and declared too with `home: <name>` written with `'`, which reads the dashed forms; its URL-encoded `’` is not read; a name outside ASCII of one or two characters cannot be checked) |
|
|
58
|
+
| `secret` | a value shaped like a credential by the contract's detector (DEC-0044), read line by line: a provider key starting a word, a JWT, a bearer token, a long random value assigned to a credential's name (but a whole Ed25519 signature under a JSON `"sig"`, `"signature"` or `"signatures"` member, as `sign` writes it, which is public); or a private key: a PEM, OpenSSH or PGP block, or a JWK's `d` as a JSON member, escaped JSON or a Python dict; a URL with a password of 8 or more characters mixing letters and digits (a password without a digit or without a letter is not read) or a credential in its query, percent-encoded or not, JSON-escaped (`\u0026`, `\/`) or nested in another URL's parameter (two levels deep); a file that is a credential by its name, as `pack` refuses it (`.env`, `*.pem`, `*.key`, `.npmrc` and the rest), or a kit credential file (`*.token`, `board.credential`) |
|
|
59
|
+
| `instance-string` | a string from the owner's list, in a file, in a file's path or in a link's target |
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python3 <plugin-dir>/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py check <project-root>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- It **never repeats** a value it found: no value, no line holding one, no entry of the owner's list and not the
|
|
66
|
+
folder it checks reaches its output. A file's path is checked like its text (line `0` of a finding), and printed
|
|
67
|
+
with an entry of the list as `<line N>`, a home folder's name as `<home>` and each segment holding a
|
|
68
|
+
credential-shaped value as `<secret>` (two files can then print the same; the segments a found value runs across, an
|
|
69
|
+
assigned value, a bearer run or a URL, are masked too, while a key, a JWT or a JWK `d` is read up to the `/` that
|
|
70
|
+
cuts it, so the rest of a cut one can print in the next folder: find the file by the folder the finding names); an instance string is named by its line in the owner's list. The report can go in a commit
|
|
71
|
+
message, an issue or a chat. A declared home of one or two letters, once found anywhere in the tree, is masked wherever it stands in a printed
|
|
72
|
+
path, inside a word too (`qzone` prints `<home>one`); found nowhere, it is not masked.
|
|
73
|
+
- It **changes nothing.** Exit `0`: no finding and every file read; `1`: at least one finding (the step is not done);
|
|
74
|
+
`2`: the check could not run, or a file was not read: an unreadable file, a nested repository, or a binary file the
|
|
75
|
+
ignore file does not name as source (the summary counts each, and how many files were left out by name). A finding
|
|
76
|
+
sets `1` even when a file was not read too; the files not read are still listed, so fixing the findings can leave `2`.
|
|
77
|
+
- **What it reads.** A text file of any size is read whole, and the summary counts every text file read; a binary file (a NUL byte in its first 8 KiB) or one that
|
|
78
|
+
cannot be read is listed by its path as not read, and the summary counts it. A database or another binary of
|
|
79
|
+
records in the tree is instance data the check cannot read: move it to the instance folder. A binary that is source
|
|
80
|
+
(an icon, a font) is named in the ignore file. `--every-file` reads every file, those Git ignores included, but `.git`, `__pycache__`, `.DS_Store` and `*.pyc`
|
|
81
|
+
(litter `pack` refuses): run it
|
|
82
|
+
on a folder before `pack`, which carries them. A committed symbolic link is checked by the path it names, never followed. When the folder is
|
|
83
|
+
not in a Git checkout, Git lists nothing in it, or no `git` is found in PATH's absolute folders (with PATH unset, in `/bin` and `/usr/bin`; on Windows, nowhere), every file is read but `.git` and `__pycache__` (uncounted) and the dependency
|
|
84
|
+
folders (`node_modules`, virtual environments, caches), and the summary says Git was not used and how many dependency folders were not read.
|
|
85
|
+
Under a locale whose file-system encoding is not UTF-8 (ISO-8859-1, KOI8-R) the check restarts itself in Python's UTF-8
|
|
86
|
+
mode, so a file name, an argument and a file's text are read in one encoding; a name that is not UTF-8 is matched as its bytes.
|
|
87
|
+
A file's text is read as UTF-8, so an entry of the owner's list with a letter outside ASCII is found in a UTF-8 file
|
|
88
|
+
(composed or decomposed, as macOS may store a name), not in one saved as Latin-1 or CP1252.
|
|
89
|
+
In Git mode a nested repository (a submodule, a vendored checkout) is not read: check a nested repository in its own tree with
|
|
90
|
+
the same check (a submodule checked out first, `git submodule update --init`), then name its path in the ignore file,
|
|
91
|
+
with that reason on the line above. That line serves the check of the outer tree only: before `pack`, check the
|
|
92
|
+
nested repository with `--every-file` in its own tree too, because `pack` carries its files, ignored ones included.
|
|
93
|
+
- **The owner's list** holds the strings only the owner knows are theirs (their name, company, accounts, hosts), one
|
|
94
|
+
per line, at least three characters; a line starting with `#` is a comment, so list a channel or a hashtag without its `#`, and a `#` after a string is
|
|
95
|
+
part of that string. An entry matches in any case and inside any word; an invisible format character (a direction mark, a
|
|
96
|
+
soft hyphen, a joiner) at either end of a string is dropped, spaces between such characters too, and does not count
|
|
97
|
+
toward the three; one inside it is kept and counts (an emoji's joiner), so a string copied with a soft hyphen in it
|
|
98
|
+
matches only text that holds one. A line `home: <name>` declares the owner's home
|
|
99
|
+
name instead (ASCII letters, digits, `.`, `_`, `-`, `'` and spaces, starting with a letter or a digit; a name holding a
|
|
100
|
+
space, as a Windows profile may, is written whole, `home: Jane Doe`, and everything after the colon is the name, so
|
|
101
|
+
a note goes on a line of its own (`# laptop`); spaces and tabs may stand around the colon; a line
|
|
102
|
+
that starts with `home` and a colon and holds anything else is refused, exit 2 (an invisible format character before
|
|
103
|
+
the name, after it or inside `home` is dropped, one inside the name refuses the line; a NUL anywhere in the list
|
|
104
|
+
refuses it too); such a line is never a string
|
|
105
|
+
entry, so list a string like `home: page` by a part of it, `ome: page`): the check then finds that home in every form the table names (it is never neutral, even with one or two
|
|
106
|
+
letters), and the bare name is not looked for anywhere else, though a declared name of three or more letters and digits is
|
|
107
|
+
masked wherever a printed path holds it as a word, its parts joined by none or any run of a space, `.`, `_`, `-`, `+`, `'`, their
|
|
108
|
+
percent-encodings or an escaped apostrophe (`\'`, `\x27`, `\u0027`, `'`, `'`, `'`), the same separators it is found with in every form; a home that is also your name goes in the list
|
|
109
|
+
as a plain string too, to be found in text. It is `--instance-strings FILE`, else the file
|
|
110
|
+
`FABRIC_INSTANCE_STRINGS` names, else `passioncode-fabric/instance-strings.txt` in the user's configuration folder
|
|
111
|
+
(`$XDG_CONFIG_HOME` when it is absolute, else `~/.config`; `%APPDATA%` on Windows). A list inside the tree is refused, because it would
|
|
112
|
+
travel with the source. A person who already keeps private terms for org-index's `check_private.py`
|
|
113
|
+
(`~/.config/passioncode/private-terms.txt`) passes that file with `--instance-strings`; this check matches a term
|
|
114
|
+
anywhere in a word, and it does not look for e-mail addresses or account ids by their shape.
|
|
115
|
+
- **Leaving paths out.** `.fabric-source-ignore` at the root (globs, one per line, `#` comments) or `--exclude GLOB`
|
|
116
|
+
leaves out files that hold synthetic values on purpose, such as test vectors. A line is a path from the root, not a
|
|
117
|
+
`.gitignore` line (a leading `/` changes nothing, a line with no `/` matches at the root only, and a line holding `\`, `//`, an unclosed `[` or a class such as `[[:digit:]]` is refused, as Git reads each
|
|
118
|
+
otherwise): `*`, `?` and `[...]` stay inside one folder (`?` and `[...]` match one byte, as Git's do, so a letter
|
|
119
|
+
outside ASCII takes two or more) and `**` as a whole segment crosses
|
|
120
|
+
folders, so `vectors/*` takes the files in `vectors` and none in its subfolders. A line ending in `/`, or a path
|
|
121
|
+
with no wildcard, takes that folder and all it holds; only such a line leaves out a nested repository or a
|
|
122
|
+
submodule (`vendor/sub/`), never a wildcard line without the `/` (`vendor/*`). A wildcard folder line (`vendor/*/`)
|
|
123
|
+
takes every nested repository it reaches, checked or not, so name each one by its path. In Git mode a nested
|
|
124
|
+
repository is not read; `--every-file` and a folder outside Git read its files, its `.git` left out. An exception line (`!`) is refused, since what it would
|
|
125
|
+
bring back would go unread. Name the files, or a folder that holds
|
|
126
|
+
only synthetic vectors, never a whole source folder (a nested repository checked in its own tree is the one
|
|
127
|
+
exception), and say why on the line above. A file `pack` refuses by its name cannot ship in a package: a public
|
|
128
|
+
certificate is renamed `*.crt`; a project `.npmrc` that holds no credential may be named in the ignore file, with
|
|
129
|
+
that reason, for the source check (the line then hides whatever is added to it later, so keep it credential-free),
|
|
130
|
+
and `pack` still refuses it, so a package is made from a copy without it (its
|
|
131
|
+
settings then apply only where the owner sets them: `~/.npmrc` reaches every project of the owner, and
|
|
132
|
+
`package.json`'s `publishConfig` carries publish-time settings such as `registry`, `tag` and `access`). A file
|
|
133
|
+
holding a credential moves to the instance, never into the ignore file.
|
|
134
|
+
- A neutral home (`example`, `user`, `runner`, a container's, a cloud machine's or a dev container's user such as `node`, `ubuntu` or `vscode`, the fixture id
|
|
135
|
+
`example-agent`, a
|
|
136
|
+
one-letter name, a placeholder; the whole set is `NEUTRAL_HOMES` in the script. An owner whose home name is in it, a
|
|
137
|
+
first name such as `alice` or an `admin` account, or has one or two letters, writes `home: <name>` in the owner's
|
|
138
|
+
list; a container's, a cloud image's or a CI runner's user that only the image uses, such as `node` or `vscode`, is never declared, since
|
|
139
|
+
its paths are the image's and stay in the source; the home of the owner's own machine is declared even when its
|
|
140
|
+
name is an image's user, as `admin` or `ubuntu` on a cloud machine the owner works on; when that name is also the
|
|
141
|
+
user of an image in the tree, the image's paths are reported, and they are written through a build argument that
|
|
142
|
+
names the user, `ARG APP_USER=ubuntu`, `USER ${APP_USER}`, `WORKDIR /home/${APP_USER}/app`, so no home path stands
|
|
143
|
+
in the source; `$HOME` serves only where a shell in the image expands it, a `RUN` line or a script, since a
|
|
144
|
+
Dockerfile's `WORKDIR` and `COPY` see only `ENV` and `ARG` values) and a value that says it is synthetic (`EXAMPLE`, `not-a-key`, `fake`, `dummy`) are
|
|
145
|
+
not findings.
|
|
146
|
+
|
|
147
|
+
The check finds the forms the table names, and only those: a credential in another shape (a bare token with no name
|
|
148
|
+
before it, a provider's key the contract's names do not cover) passes it. It is a floor under the owner's review and a
|
|
149
|
+
secret scanner, never a replacement for either.
|
|
150
|
+
|
|
151
|
+
The check finds the repository from the folder it checks: a Git location the caller exports (`GIT_DIR`,
|
|
152
|
+
`GIT_INDEX_FILE`, as a hook in a linked worktree has) is not passed to Git. The check reads the tree, not Git history. A value that was committed once is still in the history after the tree is
|
|
153
|
+
clean: handing such a source over needs a fresh history, which is the owner's act, never the agent's (no history
|
|
154
|
+
rewrite, no force-push).
|