alissa-tools-github-orcloop 0.3.4__tar.gz → 0.3.5__tar.gz
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.
- {alissa_tools_github_orcloop-0.3.4/src/main/alissa_tools_github_orcloop.egg-info → alissa_tools_github_orcloop-0.3.5}/PKG-INFO +1 -1
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/__main__.py +3 -1
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/config.py +109 -56
- alissa_tools_github_orcloop-0.3.5/src/main/alissa/tools/github/orcloop/version +1 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5/src/main/alissa_tools_github_orcloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_orcloop-0.3.4/src/main/alissa/tools/github/orcloop/version +0 -1
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/MANIFEST.in +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/README.md +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/requirements.txt +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/setup.cfg +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/setup.py +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/__init__.py +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/alissa_client.py +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/ghclient.py +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/loop.py +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/markers.py +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/proc.py +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/state.py +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa/tools/github/orcloop/version.py +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa_tools_github_orcloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa_tools_github_orcloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa_tools_github_orcloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_orcloop-0.3.4 → alissa_tools_github_orcloop-0.3.5}/src/main/alissa_tools_github_orcloop.egg-info/top_level.txt +0 -0
|
@@ -126,7 +126,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
126
126
|
action="append",
|
|
127
127
|
metavar="ACTOR_ID",
|
|
128
128
|
help="bow feed only: trust bodies of work OWNED by this Alissa actor "
|
|
129
|
-
"as release feeds; repeatable
|
|
129
|
+
"as release feeds; repeatable, and one flag may carry several ids "
|
|
130
|
+
"separated by `|` (the daemon-family convention) or `,`, the same as "
|
|
131
|
+
"--repo. When given, REPLACES the config "
|
|
130
132
|
"`bow_owners` list. OPTIONAL — left unset, the daemon resolves its own "
|
|
131
133
|
"token's actor at boot and trusts only its own containers; give this "
|
|
132
134
|
"flag only when the containers belong to someone else. Being SHARED a "
|
|
@@ -21,19 +21,36 @@ watch nothing, but boot anyway), an empty orcloop allowlist is **fatal at
|
|
|
21
21
|
startup** — the orchestrator releases work into `alissa:develop` issues and
|
|
22
22
|
closes the loop on merge, so it never runs against an unbounded repo set.
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
LIST SEPARATORS (O7, O11). **Every** list-valued key parses the same way. A
|
|
25
|
+
list written as one string — `ALISSA_ORC_REPOS`, `ALISSA_ORC_BOW_OWNERS`, a
|
|
26
|
+
`--repo a/b|c/d` flag, or a single element of the config file's list — is split
|
|
27
|
+
on **both `|` and `,`**, mixed freely, by the one shared splitter
|
|
28
|
+
(`split_entries`). `|` is the daemon **family convention** (devloop's
|
|
29
|
+
`ALISSA_DEV_REPOS` has always been pipe-separated); comma stays accepted for
|
|
30
|
+
backward compatibility with every orcloop release up to 0.3.0. The convention
|
|
31
|
+
matters because the operator points several Railway services at ONE list
|
|
32
|
+
through a variable reference —
|
|
33
|
+
`ALISSA_ORC_REPOS=${{dev.ALISSA_DEV_REPOS}}` — which only works if both daemons
|
|
34
|
+
read the same separator. Entries are whitespace-stripped, empties from
|
|
35
|
+
splitting are dropped (so a trailing separator is harmless), and duplicates
|
|
36
|
+
collapse.
|
|
37
|
+
|
|
38
|
+
What differs per key is the SHAPE check and what an empty result means — never
|
|
39
|
+
the syntax:
|
|
40
|
+
|
|
41
|
+
* `repos` entries must look like `owner/repo` (a bare word or a pasted URL is a
|
|
42
|
+
fatal startup error naming the offending entry), and an allowlist that is
|
|
43
|
+
empty *after* parsing stays fatal — fail-closed is unchanged.
|
|
44
|
+
* `bow_owners` entries must look like an Alissa actor id (see below), and an
|
|
45
|
+
empty result means "not overridden", not "trust everyone" (see FEED
|
|
46
|
+
AUTHORITY).
|
|
47
|
+
|
|
48
|
+
That the two keys once disagreed is the defect O11 closed: through 0.3.4
|
|
49
|
+
`bow_owners` split on `,` only, so an operator following the documented
|
|
50
|
+
convention got a single entry containing a `|`. Since 0.3.2's shape check that
|
|
51
|
+
at least aborted startup, but phrased as though a username had been typed;
|
|
52
|
+
before it, the entry simply matched no actor and the fail-closed feed released
|
|
53
|
+
nothing at all. Both are why the splitter is shared rather than copied.
|
|
37
54
|
|
|
38
55
|
The marker contract (`Autonomous-Dev` / `Autonomous-Scope` issue-body
|
|
39
56
|
trailers) is parsed in `markers.py`; the keys that name those trailers live
|
|
@@ -119,17 +136,17 @@ RELEASE_FEEDS = (FEED_SCAN, FEED_BOW)
|
|
|
119
136
|
# Keys carrying a boolean; env strings for these need real parsing (bool("false")
|
|
120
137
|
# is True, which would silently invert the setting).
|
|
121
138
|
_BOOL_KEYS = frozenset({"dry_run", "close_loop_enabled"})
|
|
122
|
-
# Keys carrying a list of strings.
|
|
123
|
-
#
|
|
124
|
-
#
|
|
139
|
+
# Keys carrying a list of strings. Every one of them is separator-delimited when
|
|
140
|
+
# it arrives as a single string, with the SAME separators (see LIST_SEPARATORS
|
|
141
|
+
# and `split_entries`) — a per-key syntax is exactly the drift O11 removed.
|
|
125
142
|
_LIST_KEYS = frozenset({"repos", "bow_owners"})
|
|
126
143
|
|
|
127
|
-
# The separators
|
|
128
|
-
# daemon-family convention (devloop's ALISSA_DEV_REPOS), which is what lets
|
|
129
|
-
# Railway variable reference feed both daemons; `,` is what orcloop shipped
|
|
144
|
+
# The separators any one-string list value may use, in preference order. `|` is
|
|
145
|
+
# the daemon-family convention (devloop's ALISSA_DEV_REPOS), which is what lets
|
|
146
|
+
# one Railway variable reference feed both daemons; `,` is what orcloop shipped
|
|
130
147
|
# through 0.3.0 and stays accepted. Mixing them in one value is fine.
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
LIST_SEPARATORS = ("|", ",")
|
|
149
|
+
_LIST_SPLIT_RE = re.compile("[" + re.escape("".join(LIST_SEPARATORS)) + "]")
|
|
133
150
|
|
|
134
151
|
# What an allowlist entry must look like once split: exactly one `/`, a GitHub
|
|
135
152
|
# owner (alphanumerics and hyphens, not leading/trailing) and a repo name
|
|
@@ -205,19 +222,41 @@ def _string_list(value: Any, key: str) -> tuple[str, ...]:
|
|
|
205
222
|
return tuple(value)
|
|
206
223
|
|
|
207
224
|
|
|
208
|
-
def
|
|
209
|
-
"""Split one
|
|
225
|
+
def split_entries(raw: str) -> list[str]:
|
|
226
|
+
"""Split one list-valued string into entries on `|` and/or `,`.
|
|
210
227
|
|
|
211
228
|
Whitespace around an entry is stripped and empties are dropped, so a
|
|
212
229
|
trailing separator, a doubled one, and `a/b | c/d` all behave. This is the
|
|
213
|
-
ONE place the separator set is applied: the library's
|
|
214
|
-
|
|
215
|
-
through it, so
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
230
|
+
ONE place the separator set is applied, for EVERY list key: the library's
|
|
231
|
+
env layer, the CLI flags, the config file, and the container's
|
|
232
|
+
`render_config.py` all route through it, so neither a surface nor a key can
|
|
233
|
+
end up accepting a different syntax (through 0.3.4 `bow_owners` did — see
|
|
234
|
+
the module docstring).
|
|
235
|
+
|
|
236
|
+
Splitting only — the per-key shape check is `normalize_repos` /
|
|
237
|
+
`normalize_bow_owners`, because the config file may also hand over an
|
|
238
|
+
already-split JSON list.
|
|
219
239
|
"""
|
|
220
|
-
return [entry for part in
|
|
240
|
+
return [entry for part in _LIST_SPLIT_RE.split(raw) if (entry := part.strip())]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# The 0.3.1-0.3.4 name, kept as an alias rather than deleted. The container's
|
|
244
|
+
# `render_config.py` resolves the splitter through `getattr(cfg, ...)` so that a
|
|
245
|
+
# dist too OLD to carry it dies naming it; an image built from an older
|
|
246
|
+
# docker/claude directory against a NEWER dist would hit that same branch on a
|
|
247
|
+
# pure rename and be told its library is too old, which is both false and the
|
|
248
|
+
# opposite of the fix. One line keeps that message honest. New code uses
|
|
249
|
+
# `split_entries`; there is no second implementation to drift.
|
|
250
|
+
#
|
|
251
|
+
# `REPO_SEPARATORS` — the constant renamed in the same release — deliberately
|
|
252
|
+
# gets NO alias, because the two names fail differently. The function is
|
|
253
|
+
# resolved dynamically, so a missing one produces a MISDIRECTING message; the
|
|
254
|
+
# constant was never resolved at runtime by any shipped renderer (0.3.1-0.3.4
|
|
255
|
+
# only name it in a comment), so a stale import of it is an ImportError naming
|
|
256
|
+
# the symbol — loud and self-explanatory. There is no lie for an alias to
|
|
257
|
+
# prevent there, and a `REPO_*` name for a list-neutral concept would invite the
|
|
258
|
+
# next reader straight back into the drift this release closed.
|
|
259
|
+
split_repo_entries = split_entries
|
|
221
260
|
|
|
222
261
|
|
|
223
262
|
def normalize_repos(values: tuple[str, ...] | list[str]) -> tuple[str, ...]:
|
|
@@ -236,7 +275,7 @@ def normalize_repos(values: tuple[str, ...] | list[str]) -> tuple[str, ...]:
|
|
|
236
275
|
out: list[str] = []
|
|
237
276
|
seen: set[str] = set()
|
|
238
277
|
for value in values:
|
|
239
|
-
for entry in
|
|
278
|
+
for entry in split_entries(value):
|
|
240
279
|
_validate_repo_entry(entry)
|
|
241
280
|
if (key := entry.casefold()) not in seen:
|
|
242
281
|
seen.add(key)
|
|
@@ -288,11 +327,14 @@ def _validate_repo_entry(entry: str) -> None:
|
|
|
288
327
|
def normalize_bow_owners(values: tuple[str, ...] | list[str]) -> tuple[str, ...]:
|
|
289
328
|
"""Turn the raw `bow_owners` layer value into the validated authority list.
|
|
290
329
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
330
|
+
Parsed EXACTLY like `repos`, through the same `split_entries`: an element
|
|
331
|
+
may itself carry a whole `|`- and/or `,`-separated list (that is what
|
|
332
|
+
`ALISSA_ORC_BOW_OWNERS` and a Railway variable reference look like),
|
|
333
|
+
whitespace is stripped and empties dropped, so a trailing separator is
|
|
334
|
+
harmless. Duplicates collapse EXACTLY, with no casefolding — unlike `repos`,
|
|
335
|
+
because actor ids are opaque: two spellings that differ in case are two
|
|
336
|
+
different strings on the wire, and folding them would silently widen the
|
|
337
|
+
match. Every survivor must look like an actor id.
|
|
296
338
|
|
|
297
339
|
Emptiness is fine here and means "not overridden": the boot path resolves
|
|
298
340
|
the authority to this token's own actor (see the module docstring). There is
|
|
@@ -303,13 +345,11 @@ def normalize_bow_owners(values: tuple[str, ...] | list[str]) -> tuple[str, ...]
|
|
|
303
345
|
out: list[str] = []
|
|
304
346
|
seen: set[str] = set()
|
|
305
347
|
for value in values:
|
|
306
|
-
entry
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
seen.add(entry)
|
|
312
|
-
out.append(entry)
|
|
348
|
+
for entry in split_entries(value):
|
|
349
|
+
_validate_actor_id(entry)
|
|
350
|
+
if entry not in seen:
|
|
351
|
+
seen.add(entry)
|
|
352
|
+
out.append(entry)
|
|
313
353
|
return tuple(out)
|
|
314
354
|
|
|
315
355
|
|
|
@@ -353,7 +393,18 @@ def _validate_actor_id(entry: str) -> None:
|
|
|
353
393
|
f"it contains {offending!r}, which no actor id does"
|
|
354
394
|
)
|
|
355
395
|
hint = " — " + " and ".join(problems)
|
|
356
|
-
|
|
396
|
+
parts = entry.split()
|
|
397
|
+
if len(parts) > 1 and all(_ACTOR_ID_RE.match(part) for part in parts):
|
|
398
|
+
# Splitting has already run, so an entry that STILL holds whitespace
|
|
399
|
+
# was never separated at all. Say so ONLY when every part is itself
|
|
400
|
+
# an id, which is the only evidence that this is two ids and not a
|
|
401
|
+
# display name: "Alissa Code PR Review" also contains spaces, and
|
|
402
|
+
# telling its author to fix their separator loses the hint below —
|
|
403
|
+
# the diagnostic O8 added, in the change that is about diagnostics.
|
|
404
|
+
# A mixed `<id> not-an-id` falls through here on purpose: that
|
|
405
|
+
# operator's problem is the entry, not the separator.
|
|
406
|
+
hint += "; separate several ids with `|` or `,`, not a space"
|
|
407
|
+
elif offending:
|
|
357
408
|
hint += "; that looks like a username or a display name"
|
|
358
409
|
raise ValueError(
|
|
359
410
|
f"bow_owners entry {entry!r} is not an Alissa actor id{hint}. Use the "
|
|
@@ -362,8 +413,9 @@ def _validate_actor_id(entry: str) -> None:
|
|
|
362
413
|
f"username or display name: names are renameable, so an ownership "
|
|
363
414
|
f"check against one is a spoof surface, and since no id on the wire "
|
|
364
415
|
f"ever equals a name it would fail as a silently empty feed rather "
|
|
365
|
-
f"than an error.
|
|
366
|
-
f"
|
|
416
|
+
f"than an error. Several ids are separated by `|` (the daemon-family "
|
|
417
|
+
f"convention) or `,`, the same as repos. Leave bow_owners unset to "
|
|
418
|
+
f"trust this token's own actor, which needs no id at all."
|
|
367
419
|
)
|
|
368
420
|
|
|
369
421
|
|
|
@@ -397,15 +449,13 @@ def env_overrides(environ: Mapping[str, str] | None = None) -> dict[str, Any]:
|
|
|
397
449
|
raw = env[env_name]
|
|
398
450
|
if key in _BOOL_KEYS:
|
|
399
451
|
out[key] = _env_bool(env_name, raw)
|
|
400
|
-
elif key == "repos":
|
|
401
|
-
# Separator-tolerant (`|` and `,`); shape-validated in `build`
|
|
402
|
-
# alongside the file and CLI layers, so one bad entry reports the
|
|
403
|
-
# same way wherever it came from.
|
|
404
|
-
out[key] = tuple(split_repo_entries(raw))
|
|
405
452
|
elif key in _LIST_KEYS:
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
453
|
+
# ONE branch for every list key, separator-tolerant (`|` and `,`).
|
|
454
|
+
# `repos` had its own until 0.3.4 while the rest split on `,` alone,
|
|
455
|
+
# which is precisely how the two syntaxes drifted apart. Shape is
|
|
456
|
+
# validated in `build` alongside the file and CLI layers, so one bad
|
|
457
|
+
# entry reports the same way wherever it came from.
|
|
458
|
+
out[key] = tuple(split_entries(raw))
|
|
409
459
|
else:
|
|
410
460
|
out[key] = raw
|
|
411
461
|
return out
|
|
@@ -465,8 +515,11 @@ class Config:
|
|
|
465
515
|
# allowed to OWN a feed container. The title tells the daemon which repo a
|
|
466
516
|
# container targets; it says nothing about who created it, and the daemon
|
|
467
517
|
# lists containers it merely collaborates on, which anyone in the tenant can
|
|
468
|
-
# arrange unilaterally. So provenance is decided here.
|
|
469
|
-
#
|
|
518
|
+
# arrange unilaterally. So provenance is decided here. Entries reach `build`
|
|
519
|
+
# as `|`- and/or `,`-separated text from any layer, exactly like `repos`, and
|
|
520
|
+
# land here split, shape-checked and de-duplicated — but de-duplicated
|
|
521
|
+
# EXACTLY: ids are opaque, so unlike `repos` (case-insensitive GitHub names)
|
|
522
|
+
# nothing here is casefolded, in the comparison or in the collapse.
|
|
470
523
|
#
|
|
471
524
|
# Empty means "not overridden", NOT "trust everyone": the boot path resolves
|
|
472
525
|
# it to this token's own actor and dies if it cannot (O8), and until it does
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.5
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.3.4
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|