adopt-cli 0.4.1__tar.gz → 0.4.3__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.
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/PKG-INFO +1 -1
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/pyproject.toml +1 -1
- adopt_cli-0.4.3/src/adopt_cli/_build_info.py +6 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_ask_support.py +44 -3
- adopt_cli-0.4.3/src/adopt_cli/commands/_conflict_support.py +52 -0
- adopt_cli-0.4.3/src/adopt_cli/commands/_coverage_support.py +69 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_handover_support.py +87 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_pack_support.py +4 -3
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/answer.py +23 -18
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/handover.py +78 -5
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/interchange.py +5 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/knowledge.py +90 -80
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/refresh.py +13 -8
- adopt_cli-0.4.1/src/adopt_cli/_build_info.py +0 -6
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/.gitignore +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/LICENSE +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/NOTICE +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/__init__.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/__init__.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_ci_sense_probes.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_ci_sense_support.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_draft_support.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_drafting.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_knowledge_support.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_map_support.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_probe_support.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_refresh_support.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_remote_support.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/_review_remote.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/agent.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/ask.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/boundary.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/ci_sense.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/coverage.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/detect.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/doctor.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/draft.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/freshness.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/identity.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/init.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/map_command.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/pack.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/policy.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/probe.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/pull.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/serve.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/store.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/commands/version.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/config.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/json_out.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/main.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/py.typed +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/remote.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/replica.py +0 -0
- {adopt_cli-0.4.1 → adopt_cli-0.4.3}/src/adopt_cli/store_option.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: adopt-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: The adopt command surface: config resolution, JSON mode, exit codes.
|
|
5
5
|
Project-URL: Homepage, https://github.com/onboardux/onboard-core
|
|
6
6
|
Project-URL: Source, https://github.com/onboardux/onboard-core
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Generated by scripts/embed_build_info.py; do not edit in a release build."""
|
|
2
|
+
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
SBOM_SHA256: Final[str | None] = 'e588ec7204129a71e7d334515aa3b3fe655d801ff3fc4d0198138c8b75fdd449'
|
|
6
|
+
BUILD_ID: Final[str | None] = 'github:onboardux/onboard-core:37338386536:1:c1d19202624855ec151ad4d1752e9fd0565650b7'
|
|
@@ -114,7 +114,7 @@ def answer_question(
|
|
|
114
114
|
"""
|
|
115
115
|
from adopt_ask import compose, guard, json_payload, render, retrieve
|
|
116
116
|
from adopt_ask.branch import Resolved
|
|
117
|
-
from adopt_ask.escalate import consented, escalate, may_escalate
|
|
117
|
+
from adopt_ask.escalate import consented, escalate, may_dispute, may_escalate
|
|
118
118
|
from adopt_ask.questionlog import log_question, should_log
|
|
119
119
|
from adopt_ask.synthesis import SYNTHESIS_PROMPT_REF, render_with
|
|
120
120
|
|
|
@@ -134,11 +134,22 @@ def answer_question(
|
|
|
134
134
|
[candidate.passage.revision_id for candidate in candidates]
|
|
135
135
|
)
|
|
136
136
|
|
|
137
|
+
from adopt_cli.commands._conflict_support import contradicted_now
|
|
138
|
+
|
|
137
139
|
freshness_records = handle.freshness_records()
|
|
140
|
+
# A revision a drifted probe currently contradicts is served STALE, whatever
|
|
141
|
+
# its bindings say, so `ask` agrees with the pack's "Contradicted by
|
|
142
|
+
# observation" from the moment `adopt probe run` records the conflict rather
|
|
143
|
+
# than from the next `refresh` (independence transcript T3a).
|
|
144
|
+
contradicted = contradicted_now(handle) if candidates else frozenset()
|
|
138
145
|
resolved = tuple(
|
|
139
146
|
Resolved(
|
|
140
147
|
candidate=candidate,
|
|
141
|
-
freshness=
|
|
148
|
+
freshness=_unless_contradicted(
|
|
149
|
+
resolve_freshness(freshness_records, candidate.passage.item_id, clock=clock),
|
|
150
|
+
candidate.passage.revision_id,
|
|
151
|
+
contradicted,
|
|
152
|
+
),
|
|
142
153
|
)
|
|
143
154
|
for candidate in candidates
|
|
144
155
|
)
|
|
@@ -224,10 +235,21 @@ def answer_question(
|
|
|
224
235
|
payload["escalation_id"] = escalation_id
|
|
225
236
|
payload["routed_to_plane"] = routed_to_plane
|
|
226
237
|
where = "on the control plane" if routed_to_plane else "in this store"
|
|
227
|
-
|
|
238
|
+
if may_dispute(answer):
|
|
239
|
+
human += f"\n\nReported as wrong: open question {escalation_id} {where}."
|
|
240
|
+
else:
|
|
241
|
+
human += f"\n\nRecorded as open question {escalation_id} {where}."
|
|
228
242
|
human += f'\nAnswer it with: adopt answer {escalation_id} --text "..."'
|
|
229
243
|
if routed_to_plane:
|
|
230
244
|
human += "\nIts owner has been routed a draft reply."
|
|
245
|
+
elif may_dispute(answer) and escalate_flag:
|
|
246
|
+
# Remote mode only: the plane composes its own answer, and opens no
|
|
247
|
+
# question for one it answered KNOWN. Said out loud, because the silent
|
|
248
|
+
# version of this (exit 0, nothing recorded) is the defect being fixed.
|
|
249
|
+
human += (
|
|
250
|
+
"\n\nThe control plane answered this KNOWN and recorded no dispute. "
|
|
251
|
+
"Tell the system's owner directly, or correct the cited item with `adopt review`."
|
|
252
|
+
)
|
|
231
253
|
elif may_escalate(answer) and not escalate_flag:
|
|
232
254
|
human += "\n\nRecord it as an open question with `--escalate`."
|
|
233
255
|
|
|
@@ -236,6 +258,25 @@ def answer_question(
|
|
|
236
258
|
)
|
|
237
259
|
|
|
238
260
|
|
|
261
|
+
def _unless_contradicted(resolution: Any, revision_id: str, contradicted: frozenset[str]) -> Any:
|
|
262
|
+
"""`resolution`, or STALE with `contradicted_by_observation` if a probe disagrees.
|
|
263
|
+
|
|
264
|
+
Only a resolution that would otherwise serve as KNOWN is replaced: an item
|
|
265
|
+
already stale keeps the rule that staled it, because that rule is the more
|
|
266
|
+
specific statement of what went wrong.
|
|
267
|
+
"""
|
|
268
|
+
from adopt_freshness import RULE_CONTRADICTED_BY_OBSERVATION, FreshnessResolution
|
|
269
|
+
|
|
270
|
+
if revision_id not in contradicted or resolution.state not in ("fresh", "unverified"):
|
|
271
|
+
return resolution
|
|
272
|
+
return FreshnessResolution(
|
|
273
|
+
item_id=resolution.item_id,
|
|
274
|
+
state="stale",
|
|
275
|
+
level="knowledge_revision",
|
|
276
|
+
deciding_rule=RULE_CONTRADICTED_BY_OBSERVATION,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
239
280
|
def _synthesize(answer: Any, *, question: str, config: Mapping[str, str | None]) -> Any:
|
|
240
281
|
"""One optional model call. `None` for every reason not to have made it.
|
|
241
282
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""The two reads every surface needs to agree about a probe contradiction.
|
|
2
|
+
|
|
3
|
+
`adopt gaps`, `adopt pack`, `adopt handover` and `adopt ask` all report on Bet 4's
|
|
4
|
+
`conflict` rows, and until 2026-10-05 they disagreed: the pack listed a runbook
|
|
5
|
+
as "Contradicted by observation" while `ask` served it KNOWN and fresh
|
|
6
|
+
(independence transcript T3a). Each now applies the same rule
|
|
7
|
+
(`adopt_knowledge.rank_conflicts` / `contradicted_revisions`), fed by these two
|
|
8
|
+
reads.
|
|
9
|
+
|
|
10
|
+
Both are `table_rows` reads -- Build 4's report pattern -- so no query path is
|
|
11
|
+
added to any realized port and the plane's escape-coverage denominator does not
|
|
12
|
+
move.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
__all__ = ["conflict_rows", "contradicted_now", "superseded_revisions"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def conflict_rows(handle: Any) -> tuple[Any, ...]:
|
|
21
|
+
"""Every `conflict` row in the store."""
|
|
22
|
+
from adopt_model import Conflict
|
|
23
|
+
|
|
24
|
+
return tuple(handle.export_records().table_rows("conflict", Conflict))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def superseded_revisions(handle: Any) -> frozenset[str]:
|
|
28
|
+
"""Knowledge revision ids that are no longer their item's head.
|
|
29
|
+
|
|
30
|
+
A conflict against one of these contradicts a claim nobody is making any
|
|
31
|
+
more: the item was re-confirmed or rewritten after the probe drifted.
|
|
32
|
+
"""
|
|
33
|
+
from adopt_model import KnowledgeItem, KnowledgeRevision
|
|
34
|
+
|
|
35
|
+
records = handle.export_records()
|
|
36
|
+
heads = {
|
|
37
|
+
item.current_revision_id
|
|
38
|
+
for item in records.table_rows("knowledge_item", KnowledgeItem)
|
|
39
|
+
if item.current_revision_id is not None
|
|
40
|
+
}
|
|
41
|
+
return frozenset(
|
|
42
|
+
revision.id
|
|
43
|
+
for revision in records.table_rows("knowledge_revision", KnowledgeRevision)
|
|
44
|
+
if revision.id not in heads
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def contradicted_now(handle: Any) -> frozenset[str]:
|
|
49
|
+
"""Head revisions a drifted probe currently contradicts."""
|
|
50
|
+
from adopt_knowledge import contradicted_revisions
|
|
51
|
+
|
|
52
|
+
return contradicted_revisions(conflict_rows(handle), superseded=superseded_revisions(handle))
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Keeping the coverage cache current after a command that itself changed coverage.
|
|
2
|
+
|
|
3
|
+
**The alarm was firing on the product's own writes.** `identity.covered_cache`
|
|
4
|
+
is rebuilt *from* `recompute_coverage()` and never the reverse, and any
|
|
5
|
+
disagreement between the two alarms (`COVERAGE_CACHE_DISAGREEMENT`) rather than
|
|
6
|
+
self-healing. But only `adopt coverage recompute --rebuild` ever wrote the
|
|
7
|
+
cache, so every `ingest`, `bind`, `answer`, review resolution and `refresh`
|
|
8
|
+
left it disagreeing, and the next `adopt gaps` paged about a change the product
|
|
9
|
+
had just made on purpose. An alarm that fires on every ordinary write is one an
|
|
10
|
+
operator learns to ignore, which is exactly when the real one -- a cache some
|
|
11
|
+
defect wrote -- would go unread.
|
|
12
|
+
|
|
13
|
+
**The rule that keeps the alarm meaningful: refresh only a cache that agreed.**
|
|
14
|
+
The command recomputes first. If the cache already disagreed, something other
|
|
15
|
+
than this command wrote it, and `store.py`'s incident rule applies -- rebuilding
|
|
16
|
+
a disagreeing cache destroys the evidence of whatever wrote it -- so the cache is
|
|
17
|
+
left alone and the alarm keeps firing. Only when it agreed beforehand does the
|
|
18
|
+
command rebuild it after its own writes, from a recompute that does not compare,
|
|
19
|
+
because the comparison would only report the change this command intended.
|
|
20
|
+
|
|
21
|
+
The rebuild goes through `adopt_coverage.rebuild_cache`, so `no-covered-cache-write`
|
|
22
|
+
still holds: the CLI decides *when*, and `adopt_coverage` remains the only writer.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from collections.abc import Iterator
|
|
26
|
+
from contextlib import contextmanager
|
|
27
|
+
from typing import Protocol
|
|
28
|
+
|
|
29
|
+
from adopt_coverage import CacheWriter, CoverageRecords
|
|
30
|
+
|
|
31
|
+
__all__ = ["CoverageStore", "coverage_cache_kept_current"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CoverageStore(Protocol):
|
|
35
|
+
"""The two things this module needs from a store handle, structurally.
|
|
36
|
+
|
|
37
|
+
Declared rather than importing the SQLite handle, so `no-raw-sqlite` holds:
|
|
38
|
+
`adopt_coverage`'s own ports are the whole surface.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def backend(self) -> CacheWriter: ...
|
|
43
|
+
def coverage_records(self) -> CoverageRecords: ...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@contextmanager
|
|
47
|
+
def coverage_cache_kept_current(handle: CoverageStore, system_id: str | None) -> Iterator[None]:
|
|
48
|
+
"""Wrap a command body that changes coverage inputs for `system_id`.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
handle: An open, writable store handle.
|
|
52
|
+
system_id: The system whose coverage the body may change. `None` (a
|
|
53
|
+
command that resolved no system) wraps nothing.
|
|
54
|
+
|
|
55
|
+
The post-write rebuild runs only when the body returns normally: a body that
|
|
56
|
+
raised has rolled its own transaction back, so there is nothing to follow.
|
|
57
|
+
"""
|
|
58
|
+
if system_id is None:
|
|
59
|
+
yield
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
from adopt_coverage import rebuild_cache, recompute_coverage
|
|
63
|
+
|
|
64
|
+
before = recompute_coverage(handle.coverage_records(), system_id)
|
|
65
|
+
yield
|
|
66
|
+
if before.disagreements:
|
|
67
|
+
return
|
|
68
|
+
after = recompute_coverage(handle.coverage_records(), system_id, compare_cache=False)
|
|
69
|
+
rebuild_cache(handle.backend, after)
|
|
@@ -45,11 +45,13 @@ __all__ = [
|
|
|
45
45
|
"ResolvedSystem",
|
|
46
46
|
"close_handover",
|
|
47
47
|
"current_owner_of",
|
|
48
|
+
"independence_of",
|
|
48
49
|
"open_conflicts",
|
|
49
50
|
"open_gaps",
|
|
50
51
|
"open_questions",
|
|
51
52
|
"opening_position",
|
|
52
53
|
"records_for",
|
|
54
|
+
"referent_facts",
|
|
53
55
|
"refuse_if_replica",
|
|
54
56
|
"require_open",
|
|
55
57
|
"resolve_system",
|
|
@@ -345,6 +347,91 @@ def open_conflicts(
|
|
|
345
347
|
return build_conflicts(handle, uris=uris)
|
|
346
348
|
|
|
347
349
|
|
|
350
|
+
def referent_facts(handle: Any, *, system: ResolvedSystem, uris: set[str]) -> dict[str, Any]:
|
|
351
|
+
"""What is true now of each drilled referent -- the input `validity.assess` judges.
|
|
352
|
+
|
|
353
|
+
Every read is a report read (`table_rows`, or a coverage port method that
|
|
354
|
+
already exists), so independence status adds no query path to any realized
|
|
355
|
+
port. The URI is looked up **exactly**, not through `resolve_identity`'s alias
|
|
356
|
+
walk: following a moved identity to its successor would report the successor
|
|
357
|
+
`active` and hide the very move that invalidates the drill.
|
|
358
|
+
"""
|
|
359
|
+
from adopt_handover.validity import ReferentFacts
|
|
360
|
+
|
|
361
|
+
from adopt_cli.commands._conflict_support import conflict_rows, superseded_revisions
|
|
362
|
+
from adopt_freshness import resolve_freshness
|
|
363
|
+
from adopt_model import Classification, Identity
|
|
364
|
+
|
|
365
|
+
if not uris:
|
|
366
|
+
return {}
|
|
367
|
+
records = handle.export_records()
|
|
368
|
+
by_uri = {row.uri: row for row in records.table_rows("identity", Identity) if row.uri in uris}
|
|
369
|
+
coverage = handle.coverage_records()
|
|
370
|
+
statuses = coverage.head_identity_statuses(system_id=system.system_id, environment_id=None)
|
|
371
|
+
wanted = {row.id for row in by_uri.values()}
|
|
372
|
+
|
|
373
|
+
changes: dict[str, list[tuple[str, _dt.datetime]]] = {}
|
|
374
|
+
for row in records.table_rows("classification", Classification):
|
|
375
|
+
if row.identity_id in wanted:
|
|
376
|
+
changes.setdefault(row.identity_id, []).append((str(row.class_), row.created_at))
|
|
377
|
+
|
|
378
|
+
binding_statuses = coverage.head_binding_statuses(
|
|
379
|
+
system_id=system.system_id, environment_id=None
|
|
380
|
+
)
|
|
381
|
+
verifications = coverage.head_item_verifications(system_id=system.system_id)
|
|
382
|
+
items_by_identity: dict[str, set[str]] = {}
|
|
383
|
+
for binding in coverage.bindings_in_scope(system_id=system.system_id, environment_id=None):
|
|
384
|
+
if (
|
|
385
|
+
binding.identity_id in wanted
|
|
386
|
+
and binding.is_load_bearing
|
|
387
|
+
and binding_statuses.get(binding.id) == "active"
|
|
388
|
+
and verifications.get(binding.item_id) == "verified"
|
|
389
|
+
):
|
|
390
|
+
items_by_identity.setdefault(binding.identity_id, set()).add(binding.item_id)
|
|
391
|
+
|
|
392
|
+
superseded = superseded_revisions(handle)
|
|
393
|
+
contradicted_identities = {
|
|
394
|
+
row.identity_id
|
|
395
|
+
for row in conflict_rows(handle)
|
|
396
|
+
if str(row.disposition) == "open"
|
|
397
|
+
and row.intent_revision_id is not None
|
|
398
|
+
and row.intent_revision_id not in superseded
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
freshness_records = handle.freshness_records()
|
|
402
|
+
facts: dict[str, Any] = {}
|
|
403
|
+
for uri in uris:
|
|
404
|
+
identity = by_uri.get(uri)
|
|
405
|
+
if identity is None:
|
|
406
|
+
facts[uri] = ReferentFacts(found=False)
|
|
407
|
+
continue
|
|
408
|
+
stale_rule: str | None = None
|
|
409
|
+
degraded_rule: str | None = None
|
|
410
|
+
for item_id in sorted(items_by_identity.get(identity.id, ())):
|
|
411
|
+
resolution = resolve_freshness(freshness_records, item_id)
|
|
412
|
+
if resolution.state == "stale" and stale_rule is None:
|
|
413
|
+
stale_rule = resolution.deciding_rule
|
|
414
|
+
elif resolution.state == "observation_stale" and degraded_rule is None:
|
|
415
|
+
degraded_rule = resolution.deciding_rule
|
|
416
|
+
facts[uri] = ReferentFacts(
|
|
417
|
+
found=True,
|
|
418
|
+
status=statuses.get(identity.id),
|
|
419
|
+
changes=tuple(changes.get(identity.id, ())),
|
|
420
|
+
contradicted=identity.id in contradicted_identities,
|
|
421
|
+
stale_rule=stale_rule,
|
|
422
|
+
degraded_rule=degraded_rule,
|
|
423
|
+
)
|
|
424
|
+
return facts
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def independence_of(handle: Any, *, system: ResolvedSystem, record: Any) -> Any:
|
|
428
|
+
"""Each passed exit test's validity now (`adopt_handover.validity`)."""
|
|
429
|
+
from adopt_handover.validity import assess, passed_tasks
|
|
430
|
+
|
|
431
|
+
uris = {task.uri for task in passed_tasks(record) if task.uri is not None}
|
|
432
|
+
return assess(record, referent_facts(handle, system=system, uris=uris))
|
|
433
|
+
|
|
434
|
+
|
|
348
435
|
def open_questions(handle: Any, *, system_id: str) -> tuple[OpenQuestion, ...]:
|
|
349
436
|
"""Unanswered questions for this system, newest first."""
|
|
350
437
|
return tuple(
|
|
@@ -203,9 +203,8 @@ def build_conflicts(handle: Any, *, uris: dict[str, str]) -> tuple[PackConflict,
|
|
|
203
203
|
"""
|
|
204
204
|
from adopt_knowledge import rank_conflicts
|
|
205
205
|
|
|
206
|
-
from
|
|
206
|
+
from adopt_cli.commands._conflict_support import conflict_rows, superseded_revisions
|
|
207
207
|
|
|
208
|
-
rows = handle.export_records().table_rows("conflict", Conflict)
|
|
209
208
|
return tuple(
|
|
210
209
|
PackConflict(
|
|
211
210
|
uri=conflict.uri,
|
|
@@ -213,7 +212,9 @@ def build_conflicts(handle: Any, *, uris: dict[str, str]) -> tuple[PackConflict,
|
|
|
213
212
|
intent_revision_id=conflict.intent_revision_id,
|
|
214
213
|
detected_at=conflict.detected_at,
|
|
215
214
|
)
|
|
216
|
-
for conflict in rank_conflicts(
|
|
215
|
+
for conflict in rank_conflicts(
|
|
216
|
+
conflict_rows(handle), uris, superseded=superseded_revisions(handle)
|
|
217
|
+
)
|
|
217
218
|
)
|
|
218
219
|
|
|
219
220
|
|
|
@@ -169,24 +169,29 @@ def answer(
|
|
|
169
169
|
identity_ids, unmatched = identities_to_bind(
|
|
170
170
|
text, identity_views(handle, resolved_scope), explicit_uris=uri or []
|
|
171
171
|
)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
172
|
+
from adopt_cli.commands._coverage_support import coverage_cache_kept_current
|
|
173
|
+
|
|
174
|
+
system_id = resolved_scope.system.id if resolved_scope.system is not None else None
|
|
175
|
+
with coverage_cache_kept_current(handle, system_id):
|
|
176
|
+
result = capture_answer(
|
|
177
|
+
_StoreAdapter(handle),
|
|
178
|
+
escalation_id=escalation_id,
|
|
179
|
+
scope=resolved_scope,
|
|
180
|
+
# The question is the title, so the captured row is findable by
|
|
181
|
+
# the words somebody actually asked. Falling back to the answer's
|
|
182
|
+
# first line would title it with whatever the human happened to
|
|
183
|
+
# type first, which is not what the next asker will search for.
|
|
184
|
+
title=title or existing.question or f"Answer to {escalation_id}",
|
|
185
|
+
body_md=text,
|
|
186
|
+
identity_ids=identity_ids,
|
|
187
|
+
# `DEFAULT_AUDIENCES` rather than a literal here: the default
|
|
188
|
+
# lives in `adopt_ask.capture` beside the rule it protects, so the
|
|
189
|
+
# CLI and the plane cannot drift into tagging captured answers
|
|
190
|
+
# differently.
|
|
191
|
+
audiences=tuple(audience) if audience else DEFAULT_AUDIENCES,
|
|
192
|
+
actor_id=actor,
|
|
193
|
+
unmatched_uris=unmatched,
|
|
194
|
+
)
|
|
190
195
|
|
|
191
196
|
payload = {
|
|
192
197
|
"escalation_id": result.escalation_id,
|
|
@@ -64,6 +64,14 @@ OutOption = Annotated[
|
|
|
64
64
|
]
|
|
65
65
|
StoreOption = Annotated[Path | None, typer.Option("--store", help="Path to the store.")]
|
|
66
66
|
JsonOption = Annotated[bool, typer.Option("--json", help="Machine-readable output.")]
|
|
67
|
+
StrictOption = Annotated[
|
|
68
|
+
bool,
|
|
69
|
+
typer.Option(
|
|
70
|
+
"--strict",
|
|
71
|
+
help="Exit 4 when any exit test the receiving team passed has since been invalidated "
|
|
72
|
+
"by a change. For a pipeline that gates on independence; off by default.",
|
|
73
|
+
),
|
|
74
|
+
]
|
|
67
75
|
AudienceOption = Annotated[
|
|
68
76
|
list[str] | None,
|
|
69
77
|
typer.Option("--audience", help="Emit this audience. Repeatable; defaults to all four."),
|
|
@@ -433,8 +441,18 @@ def verify(
|
|
|
433
441
|
question=question_for(task),
|
|
434
442
|
)
|
|
435
443
|
opened.append({"task_id": task.id, "escalation_id": escalation_id})
|
|
444
|
+
# `uri` is what lets the result go stale: it names the referent the
|
|
445
|
+
# task exercised, so a later change to it invalidates this pass
|
|
446
|
+
# (`adopt_handover.validity`). Until 2026-10-05 it was parsed and
|
|
447
|
+
# then dropped, and a passed task could never be invalidated.
|
|
436
448
|
outcomes.append(
|
|
437
|
-
{
|
|
449
|
+
{
|
|
450
|
+
"id": task.id,
|
|
451
|
+
"outcome": task.outcome,
|
|
452
|
+
"escalation_id": escalation_id,
|
|
453
|
+
"uri": task.uri,
|
|
454
|
+
"performed_by": task.performed_by,
|
|
455
|
+
}
|
|
438
456
|
)
|
|
439
457
|
|
|
440
458
|
detail = {
|
|
@@ -644,14 +662,21 @@ def close(
|
|
|
644
662
|
def status(
|
|
645
663
|
system: SystemOption = None,
|
|
646
664
|
scope: ScopeOption = None,
|
|
665
|
+
strict: StrictOption = False,
|
|
647
666
|
store: StoreOption = None,
|
|
648
667
|
json_output: JsonOption = False,
|
|
649
668
|
) -> None:
|
|
650
|
-
"""Every step recorded, what comes next,
|
|
669
|
+
"""Every step recorded, what comes next, what is still open -- and what still holds.
|
|
651
670
|
|
|
652
671
|
Reads only, and answers for a closed handover as readily as an open one --
|
|
653
672
|
the record is what both parties keep, and it must still describe itself a
|
|
654
673
|
year later.
|
|
674
|
+
|
|
675
|
+
**Independence leads.** Each exit test the receiving team passed is judged
|
|
676
|
+
against what has changed since (`adopt_handover.validity`): `valid`,
|
|
677
|
+
`invalidated` with its cause, `degraded`, or `unanchored` when the task named
|
|
678
|
+
no `uri`. A drill result that could not go stale would be a certificate;
|
|
679
|
+
this is the proof that says when it stopped being true.
|
|
655
680
|
"""
|
|
656
681
|
from adopt_handover import VERB_FOR, record_payload
|
|
657
682
|
|
|
@@ -700,12 +725,18 @@ def status(
|
|
|
700
725
|
system_owner=owner,
|
|
701
726
|
written_by=writer_identity(),
|
|
702
727
|
),
|
|
728
|
+
"independence": _independence_payload(
|
|
729
|
+
support.independence_of(handle, system=resolved, record=record)
|
|
730
|
+
),
|
|
703
731
|
}
|
|
704
732
|
finally:
|
|
705
733
|
handle.close()
|
|
706
734
|
|
|
735
|
+
invalidated = bool(payload.get("independence", {}).get("invalidated"))
|
|
707
736
|
if json_output:
|
|
708
737
|
emit(payload, as_json=True)
|
|
738
|
+
if strict and invalidated:
|
|
739
|
+
raise typer.Exit(4) # const-sync: ok -- exit code, not a version.
|
|
709
740
|
return
|
|
710
741
|
if payload["state"] == "none":
|
|
711
742
|
typer.echo(
|
|
@@ -713,12 +744,22 @@ def status(
|
|
|
713
744
|
"`adopt handover start --receiving-owner <group>` opens one."
|
|
714
745
|
)
|
|
715
746
|
return
|
|
747
|
+
independence = payload["independence"]
|
|
716
748
|
lines = [
|
|
717
749
|
f"Handover {payload['handover_id']} [{payload['state']}] {payload['scope']}",
|
|
718
|
-
f"
|
|
719
|
-
f" owner now: {payload['current_owner'] or 'nobody'}",
|
|
720
|
-
"",
|
|
750
|
+
f" independence: {independence['summary']}",
|
|
721
751
|
]
|
|
752
|
+
for task in independence["tasks"]:
|
|
753
|
+
if task["status"] != "valid":
|
|
754
|
+
since = f" since {task['since']}" if task["since"] else ""
|
|
755
|
+
lines.append(f" {task['status']}: {task['task_id']} ({task['cause']}){since}")
|
|
756
|
+
lines.extend(
|
|
757
|
+
[
|
|
758
|
+
f" receiving owner: {payload['receiving_owner']}",
|
|
759
|
+
f" owner now: {payload['current_owner'] or 'nobody'}",
|
|
760
|
+
"",
|
|
761
|
+
]
|
|
762
|
+
)
|
|
722
763
|
for step in payload["steps"]:
|
|
723
764
|
mark = "x" if step["done"] else " "
|
|
724
765
|
when = f" {step['at']}" if step["at"] else ""
|
|
@@ -740,6 +781,38 @@ def status(
|
|
|
740
781
|
"unreadable detail; the trail was edited outside this product."
|
|
741
782
|
)
|
|
742
783
|
typer.echo("\n".join(lines))
|
|
784
|
+
if strict and invalidated:
|
|
785
|
+
raise typer.Exit(4) # const-sync: ok -- exit code, not a version.
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _independence_payload(independence: Any) -> dict[str, Any]:
|
|
789
|
+
"""The `independence` block of `adopt handover status --json`."""
|
|
790
|
+
from adopt_handover.validity import DEGRADED, INVALIDATED, UNANCHORED, VALID
|
|
791
|
+
|
|
792
|
+
from adopt_obs import format_timestamp
|
|
793
|
+
|
|
794
|
+
def stamp(value: Any) -> str | None:
|
|
795
|
+
return None if value is None else format_timestamp(value)
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
"summary": independence.summary,
|
|
799
|
+
"passed": independence.passed,
|
|
800
|
+
"valid": independence.count(VALID),
|
|
801
|
+
"invalidated": independence.count(INVALIDATED),
|
|
802
|
+
"degraded": independence.count(DEGRADED),
|
|
803
|
+
"unanchored": independence.count(UNANCHORED),
|
|
804
|
+
"tasks": [
|
|
805
|
+
{
|
|
806
|
+
"task_id": task.task_id,
|
|
807
|
+
"uri": task.uri,
|
|
808
|
+
"status": task.status,
|
|
809
|
+
"cause": task.cause,
|
|
810
|
+
"verified_at": stamp(task.verified_at),
|
|
811
|
+
"since": stamp(task.since),
|
|
812
|
+
}
|
|
813
|
+
for task in independence.tasks
|
|
814
|
+
],
|
|
815
|
+
}
|
|
743
816
|
|
|
744
817
|
|
|
745
818
|
def _write_acceptance(
|
|
@@ -87,6 +87,11 @@ def import_(
|
|
|
87
87
|
json_output: JsonOption = False,
|
|
88
88
|
) -> None:
|
|
89
89
|
"""Verify a bundle whole, then restore it into an empty store."""
|
|
90
|
+
# `--into` is "created empty if absent", and that has to include its
|
|
91
|
+
# directory: the restore is often a beneficiary's first command on their own
|
|
92
|
+
# machine, and SQLite refuses a path whose parent does not exist with a raw
|
|
93
|
+
# `OperationalError` rather than anything an operator can act on.
|
|
94
|
+
into.parent.mkdir(parents=True, exist_ok=True)
|
|
90
95
|
with open_named_store(into, migrate=True) as handle:
|
|
91
96
|
manifest = apply_bundle(handle.import_records(), directory)
|
|
92
97
|
emit(_payload(directory, manifest), as_json=json_output, title="adopt import")
|
|
@@ -60,6 +60,7 @@ def ingest(
|
|
|
60
60
|
"""Turn documents into knowledge, bound to the identities they refer to."""
|
|
61
61
|
from adopt_knowledge import discover, run_ingest
|
|
62
62
|
|
|
63
|
+
from adopt_cli.commands._coverage_support import coverage_cache_kept_current
|
|
63
64
|
from adopt_cli.commands._knowledge_support import (
|
|
64
65
|
StoreUnitOfWork,
|
|
65
66
|
bound_pairs,
|
|
@@ -73,19 +74,21 @@ def ingest(
|
|
|
73
74
|
try:
|
|
74
75
|
resolved = resolve_scope(handle, scope)
|
|
75
76
|
documents = discover(paths, root=Path.cwd(), audience=audience)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
system_id = resolved.system.id if resolved.system is not None else None
|
|
78
|
+
with coverage_cache_kept_current(handle, system_id):
|
|
79
|
+
report = run_ingest(
|
|
80
|
+
documents,
|
|
81
|
+
scope=resolved,
|
|
82
|
+
identities=identity_views(handle, resolved),
|
|
83
|
+
stored=stored_documents(handle, resolved),
|
|
84
|
+
knowledge=handle.items(),
|
|
85
|
+
bindings=handle.bindings(),
|
|
86
|
+
reviews=handle.governance(),
|
|
87
|
+
unit=StoreUnitOfWork(handle),
|
|
88
|
+
bound_pairs=bound_pairs(handle),
|
|
89
|
+
presented_revisions=presented_revisions(handle),
|
|
90
|
+
actor_id=actor,
|
|
91
|
+
)
|
|
89
92
|
payload = _ingest_payload(report)
|
|
90
93
|
finally:
|
|
91
94
|
handle.close()
|
|
@@ -307,14 +310,17 @@ def bind(
|
|
|
307
310
|
hint="Run `adopt map` first. A moved identity's old URI still resolves, so "
|
|
308
311
|
"this is genuine absence rather than a stale address.",
|
|
309
312
|
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
313
|
+
from adopt_cli.commands._coverage_support import coverage_cache_kept_current
|
|
314
|
+
|
|
315
|
+
with coverage_cache_kept_current(handle, identity.system_id):
|
|
316
|
+
binding_id, revision_id = handle.bindings().bind(
|
|
317
|
+
item_id=knowledge_id,
|
|
318
|
+
identity_id=identity.id,
|
|
319
|
+
is_load_bearing=not not_load_bearing,
|
|
320
|
+
extractor=EXTRACTOR_MANUAL,
|
|
321
|
+
extractor_version=INGEST_EXTRACTOR_VERSION,
|
|
322
|
+
actor_id=actor,
|
|
323
|
+
)
|
|
318
324
|
payload = {
|
|
319
325
|
"binding": binding_id,
|
|
320
326
|
"revision": revision_id,
|
|
@@ -499,12 +505,13 @@ def _conflicts_payload(handle: Any, result: Any, ranker: Any) -> list[dict[str,
|
|
|
499
505
|
coverage just evaluated, so a conflict recorded against another system cannot
|
|
500
506
|
appear in this scope's queue.
|
|
501
507
|
"""
|
|
502
|
-
from
|
|
508
|
+
from adopt_cli.commands._conflict_support import conflict_rows, superseded_revisions
|
|
503
509
|
|
|
504
510
|
if result is None:
|
|
505
511
|
return []
|
|
506
512
|
uris = {row.identity_id: row.uri for row in result.identities}
|
|
507
|
-
rows = handle
|
|
513
|
+
rows = conflict_rows(handle)
|
|
514
|
+
superseded = superseded_revisions(handle)
|
|
508
515
|
return [
|
|
509
516
|
{
|
|
510
517
|
"uri": conflict.uri,
|
|
@@ -516,7 +523,7 @@ def _conflicts_payload(handle: Any, result: Any, ranker: Any) -> list[dict[str,
|
|
|
516
523
|
# exists, and so a reader of the JSON never has to assume.
|
|
517
524
|
"disposition": "open",
|
|
518
525
|
}
|
|
519
|
-
for conflict in ranker(rows, uris)
|
|
526
|
+
for conflict in ranker(rows, uris, superseded=superseded)
|
|
520
527
|
]
|
|
521
528
|
|
|
522
529
|
|
|
@@ -684,68 +691,71 @@ def review(
|
|
|
684
691
|
title="adopt review",
|
|
685
692
|
)
|
|
686
693
|
return
|
|
694
|
+
from adopt_cli.commands._coverage_support import coverage_cache_kept_current
|
|
695
|
+
|
|
687
696
|
handle = open_configured_store(store, read_only=not writing, verb="review")
|
|
688
697
|
try:
|
|
689
698
|
resolved = resolve_scope(handle, scope)
|
|
690
699
|
identities = identity_views(handle, resolved)
|
|
691
700
|
pending = pending_items(handle, resolved, identities)
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
resolutions: list[dict[str, Any]] = []
|
|
716
|
-
for item, action in targets:
|
|
717
|
-
if action == REJECT:
|
|
718
|
-
outcome = reject_pending(item, reviews=handle.governance())
|
|
719
|
-
elif action == CORRECT:
|
|
720
|
-
outcome = edit_pending(
|
|
721
|
-
item,
|
|
722
|
-
reviews=handle.governance(),
|
|
723
|
-
knowledge=handle.items(),
|
|
724
|
-
unit=StoreUnitOfWork(handle),
|
|
725
|
-
body_md=body_md,
|
|
726
|
-
source_ref=str(file),
|
|
727
|
-
actor_id=actor,
|
|
728
|
-
)
|
|
729
|
-
else:
|
|
730
|
-
outcome = confirm_pending(
|
|
731
|
-
item,
|
|
732
|
-
reviews=handle.governance(),
|
|
733
|
-
bindings=handle.bindings(),
|
|
734
|
-
unit=StoreUnitOfWork(handle),
|
|
735
|
-
knowledge=handle.items(),
|
|
736
|
-
bound_pairs=bound_pairs(handle),
|
|
737
|
-
actor_id=actor,
|
|
738
|
-
)
|
|
739
|
-
resolutions.append(
|
|
740
|
-
{
|
|
741
|
-
"review_item": item.review_item_id,
|
|
742
|
-
"action": outcome.resolution,
|
|
743
|
-
"source": item.source,
|
|
744
|
-
"bindings": len(outcome.bindings),
|
|
745
|
-
"revision": outcome.revision_id,
|
|
746
|
-
}
|
|
701
|
+
system_id = resolved.system.id if writing and resolved.system is not None else None
|
|
702
|
+
with coverage_cache_kept_current(handle, system_id):
|
|
703
|
+
if resolve_item:
|
|
704
|
+
payload = _resolve_change(
|
|
705
|
+
handle,
|
|
706
|
+
pending,
|
|
707
|
+
known_review_items(handle),
|
|
708
|
+
review_item_id=resolve_item,
|
|
709
|
+
action=action or "",
|
|
710
|
+
to_uri=to_uri,
|
|
711
|
+
actor=actor,
|
|
712
|
+
)
|
|
713
|
+
elif not writing:
|
|
714
|
+
payload = _queue_payload(pending)
|
|
715
|
+
payload.update(_refresh_payload(refresh_population(handle, resolved, pending)))
|
|
716
|
+
else:
|
|
717
|
+
targets = _targets(
|
|
718
|
+
pending,
|
|
719
|
+
known_review_items(handle),
|
|
720
|
+
confirm_item,
|
|
721
|
+
reject_item,
|
|
722
|
+
edit_item,
|
|
723
|
+
confirm_batch,
|
|
747
724
|
)
|
|
748
|
-
|
|
725
|
+
resolutions: list[dict[str, Any]] = []
|
|
726
|
+
for item, action in targets:
|
|
727
|
+
if action == REJECT:
|
|
728
|
+
outcome = reject_pending(item, reviews=handle.governance())
|
|
729
|
+
elif action == CORRECT:
|
|
730
|
+
outcome = edit_pending(
|
|
731
|
+
item,
|
|
732
|
+
reviews=handle.governance(),
|
|
733
|
+
knowledge=handle.items(),
|
|
734
|
+
unit=StoreUnitOfWork(handle),
|
|
735
|
+
body_md=body_md,
|
|
736
|
+
source_ref=str(file),
|
|
737
|
+
actor_id=actor,
|
|
738
|
+
)
|
|
739
|
+
else:
|
|
740
|
+
outcome = confirm_pending(
|
|
741
|
+
item,
|
|
742
|
+
reviews=handle.governance(),
|
|
743
|
+
bindings=handle.bindings(),
|
|
744
|
+
unit=StoreUnitOfWork(handle),
|
|
745
|
+
knowledge=handle.items(),
|
|
746
|
+
bound_pairs=bound_pairs(handle),
|
|
747
|
+
actor_id=actor,
|
|
748
|
+
)
|
|
749
|
+
resolutions.append(
|
|
750
|
+
{
|
|
751
|
+
"review_item": item.review_item_id,
|
|
752
|
+
"action": outcome.resolution,
|
|
753
|
+
"source": item.source,
|
|
754
|
+
"bindings": len(outcome.bindings),
|
|
755
|
+
"revision": outcome.revision_id,
|
|
756
|
+
}
|
|
757
|
+
)
|
|
758
|
+
payload = {"resolved": len(resolutions), "resolutions": resolutions}
|
|
749
759
|
finally:
|
|
750
760
|
handle.close()
|
|
751
761
|
|
|
@@ -152,14 +152,19 @@ def refresh(
|
|
|
152
152
|
allow_network=allow_network,
|
|
153
153
|
)
|
|
154
154
|
)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
155
|
+
from adopt_cli.commands._coverage_support import coverage_cache_kept_current
|
|
156
|
+
|
|
157
|
+
with coverage_cache_kept_current(
|
|
158
|
+
handle, resolved.system.id if resolved.system is not None else None
|
|
159
|
+
):
|
|
160
|
+
outcome = record_refresh(
|
|
161
|
+
handle,
|
|
162
|
+
scope=resolved,
|
|
163
|
+
diff=diff,
|
|
164
|
+
batch_key=batch_key,
|
|
165
|
+
probes=probes,
|
|
166
|
+
actor_id=actor,
|
|
167
|
+
)
|
|
163
168
|
outcome.files_hashed = files_hashed
|
|
164
169
|
# After the canonical write commits, never before: a snapshot saved
|
|
165
170
|
# first would let a crash between the two leave the store claiming this
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
"""Generated by scripts/embed_build_info.py; do not edit in a release build."""
|
|
2
|
-
|
|
3
|
-
from typing import Final
|
|
4
|
-
|
|
5
|
-
SBOM_SHA256: Final[str | None] = '6742b345c4d8f4d20905a7e8ca33afd66dd98e023cb9d13fb8982d3f2a3cd493'
|
|
6
|
-
BUILD_ID: Final[str | None] = 'github:onboardux/onboard-core:34466475060:1:d639bc04586bda49c77f2b8c71865ceba74f2a0b'
|
|
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
|
|
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
|
|
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
|