moca-reader 0.6.0__py3-none-any.whl
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.
- moca_reader/__init__.py +84 -0
- moca_reader/_js.py +291 -0
- moca_reader/attestations.py +271 -0
- moca_reader/audience.py +49 -0
- moca_reader/backends/__init__.py +1 -0
- moca_reader/backends/bm25.py +118 -0
- moca_reader/backends/dense.py +146 -0
- moca_reader/backends/hybrid.py +73 -0
- moca_reader/backends/lexical.py +112 -0
- moca_reader/backends/memory_store.py +122 -0
- moca_reader/citation.py +213 -0
- moca_reader/codes.py +110 -0
- moca_reader/conformance.py +249 -0
- moca_reader/content.py +312 -0
- moca_reader/diagnostics.py +45 -0
- moca_reader/digest.py +92 -0
- moca_reader/dsse.py +97 -0
- moca_reader/ed25519.py +83 -0
- moca_reader/embedders.py +224 -0
- moca_reader/evidence_text.py +187 -0
- moca_reader/frontmatter.py +151 -0
- moca_reader/library.py +176 -0
- moca_reader/manifest.py +186 -0
- moca_reader/metadata.py +58 -0
- moca_reader/profiles.py +5 -0
- moca_reader/py.typed +0 -0
- moca_reader/reader.py +468 -0
- moca_reader/relations.py +46 -0
- moca_reader/resolver.py +33 -0
- moca_reader/reviews.py +18 -0
- moca_reader/schemas/ag-ui-citations.schema.json +36 -0
- moca_reader/schemas/citation-record.schema.json +326 -0
- moca_reader/schemas/manifest.schema.json +171 -0
- moca_reader/schemas/node.schema.json +372 -0
- moca_reader/schemas/review-predicate.schema.json +42 -0
- moca_reader/schemas/search-hit.schema.json +62 -0
- moca_reader/schemas/sidecar-index.schema.json +155 -0
- moca_reader/schemas/structure.schema.json +93 -0
- moca_reader/schemas/trust-root.schema.json +130 -0
- moca_reader/schemas.py +44 -0
- moca_reader/search.py +346 -0
- moca_reader/sidecar.py +228 -0
- moca_reader/source.py +285 -0
- moca_reader/state.py +119 -0
- moca_reader/strategies/__init__.py +1 -0
- moca_reader/strategies/diversify.py +28 -0
- moca_reader/strategies/ontology_guided.py +63 -0
- moca_reader/structure.py +385 -0
- moca_reader/structure_ops.py +160 -0
- moca_reader/unicode.py +33 -0
- moca_reader-0.6.0.dist-info/METADATA +209 -0
- moca_reader-0.6.0.dist-info/RECORD +55 -0
- moca_reader-0.6.0.dist-info/WHEEL +4 -0
- moca_reader-0.6.0.dist-info/licenses/LICENSE +201 -0
- moca_reader-0.6.0.dist-info/licenses/NOTICE +15 -0
moca_reader/library.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""A library of loaded packages: citation records, relations between packages, and their merged structure with
|
|
2
|
+
the host's overlays (Reader contract §7-§10). Search over a library is in search.py."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from datetime import UTC, datetime
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from ._js import get, js_string
|
|
13
|
+
from .citation import CitationRecord, RelationIndex, citation_for, pick_representation
|
|
14
|
+
from .diagnostics import Diagnostics
|
|
15
|
+
from .relations import relation_index_of
|
|
16
|
+
from .source import DEFAULT_LIMITS
|
|
17
|
+
from .structure import LAYERS, Fact, parse_structure
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .reader import PackageResult
|
|
21
|
+
from .structure_ops import StructureIndex
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class Overlay:
|
|
26
|
+
"""Host-supplied structure (moca-spec ADR-0008), never package content: Turtle text or a file."""
|
|
27
|
+
|
|
28
|
+
id: str
|
|
29
|
+
layer: str # application | organisation
|
|
30
|
+
source: str | bytes | None = None
|
|
31
|
+
path: str | os.PathLike[str] | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class LoadedPackage:
|
|
36
|
+
result: PackageResult
|
|
37
|
+
chunks: list[dict[str, Any]] | None = None
|
|
38
|
+
index: Any = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Library:
|
|
42
|
+
"""Holds verified packages, with their sidecar chunks when bound."""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
clock: Callable[[], datetime] | None = None,
|
|
47
|
+
overlays: list[Overlay] | None = None,
|
|
48
|
+
limits: dict[str, int] | None = None,
|
|
49
|
+
allow_unsigned_relations: bool = False,
|
|
50
|
+
) -> None:
|
|
51
|
+
self.clock: Callable[[], datetime] = clock or (lambda: datetime.now(UTC))
|
|
52
|
+
# Package spec §7.2: off by default. Only for libraries of the host's own packages.
|
|
53
|
+
self.allow_unsigned_relations = allow_unsigned_relations
|
|
54
|
+
self.packages: list[LoadedPackage] = []
|
|
55
|
+
self.diagnostics = Diagnostics()
|
|
56
|
+
self.overlay_facts: list[Fact] = []
|
|
57
|
+
limit_values = {**DEFAULT_LIMITS, **(limits or {})}
|
|
58
|
+
for o in overlays or []:
|
|
59
|
+
if o.layer not in ("application", "organisation"):
|
|
60
|
+
raise ValueError(f"overlay {o.id}: layer must be application or organisation")
|
|
61
|
+
text: str | bytes
|
|
62
|
+
if o.source is not None:
|
|
63
|
+
text = o.source
|
|
64
|
+
elif o.path is not None:
|
|
65
|
+
with open(o.path, "rb") as f:
|
|
66
|
+
text = f.read()
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError(f"overlay {o.id}: give its source or its path")
|
|
69
|
+
facts, _, _ = parse_structure(
|
|
70
|
+
text, f"overlay:{o.id}", o.layer, f"overlay:{o.id}", self.diagnostics, limit_values
|
|
71
|
+
)
|
|
72
|
+
self.overlay_facts.extend(facts)
|
|
73
|
+
self.overlay_facts.sort(key=lambda f: LAYERS.index(f["layer"]))
|
|
74
|
+
self._structure: StructureIndex | None = None
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def structure(self) -> StructureIndex:
|
|
78
|
+
"""The merged structure of every loaded package and overlay (Reader contract §10)."""
|
|
79
|
+
from .structure_ops import StructureIndex
|
|
80
|
+
|
|
81
|
+
if self._structure is None:
|
|
82
|
+
self._structure = StructureIndex(self, self.overlay_facts)
|
|
83
|
+
return self._structure
|
|
84
|
+
|
|
85
|
+
def add(self, result: PackageResult, chunks: list[dict[str, Any]] | None = None, index: Any = None) -> Library:
|
|
86
|
+
"""Adds a package read by read_package(), with a bound sidecar's chunks and index. Invalid packages are refused."""
|
|
87
|
+
if not result.valid or not result.digest:
|
|
88
|
+
raise ValueError(f"refusing to add an invalid package: {get(result.manifest, 'id') or '(unknown)'}")
|
|
89
|
+
key = _key(result)
|
|
90
|
+
same = next((p for p in self.packages if _key(p.result) == key), None)
|
|
91
|
+
if same is not None:
|
|
92
|
+
# Contract §7: one id@version, one digest. A second digest is refused and reported.
|
|
93
|
+
if same.result.digest != result.digest:
|
|
94
|
+
self.diagnostics.add(
|
|
95
|
+
"L001_IDENTITY_CONFLICT",
|
|
96
|
+
f"{key} is already loaded with digest {same.result.digest}; {result.digest} is refused",
|
|
97
|
+
)
|
|
98
|
+
return self
|
|
99
|
+
self.packages.append(LoadedPackage(result, chunks, index))
|
|
100
|
+
self._structure = None
|
|
101
|
+
# A composed package brings its verified members with it.
|
|
102
|
+
for member in result.members:
|
|
103
|
+
if member.status == "resolved" and member.result is not None and member.result.valid:
|
|
104
|
+
self.add(member.result)
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
def relation_index(self) -> RelationIndex:
|
|
108
|
+
"""Package-level relation effects among the loaded packages (package spec §7.2)."""
|
|
109
|
+
return relation_index_of(
|
|
110
|
+
[
|
|
111
|
+
{
|
|
112
|
+
"id": p.result.manifest.get("id"),
|
|
113
|
+
"version": p.result.manifest.get("version"),
|
|
114
|
+
"relations": p.result.manifest.get("relations"),
|
|
115
|
+
"relationScopes": p.result.relation_scopes,
|
|
116
|
+
}
|
|
117
|
+
for p in self.packages
|
|
118
|
+
],
|
|
119
|
+
self.allow_unsigned_relations,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def citations(self, locale: str | None = None, include_text: bool = False) -> list[CitationRecord]:
|
|
123
|
+
"""Citation records for every node: one per node, in the requested locale."""
|
|
124
|
+
index = self.relation_index()
|
|
125
|
+
now = self.clock()
|
|
126
|
+
out = []
|
|
127
|
+
for p in self.packages:
|
|
128
|
+
for node in p.result.nodes:
|
|
129
|
+
rep = pick_representation(node, locale)
|
|
130
|
+
out.append(citation_for(p.result, node, rep, now, index, rep.body if include_text else None))
|
|
131
|
+
return out
|
|
132
|
+
|
|
133
|
+
def get(self, node_ref: str, locale: str | None = None, include_text: bool = True) -> CitationRecord | None:
|
|
134
|
+
"""A node by its id (``<package id>#<path>``) or versioned reference (``<package id>@<version>#<path>``).
|
|
135
|
+
|
|
136
|
+
An id matches the first loaded version.
|
|
137
|
+
"""
|
|
138
|
+
hash_at = node_ref.rfind("#")
|
|
139
|
+
if hash_at < 0:
|
|
140
|
+
return None
|
|
141
|
+
path, head = node_ref[hash_at + 1 :], node_ref[:hash_at]
|
|
142
|
+
index = self.relation_index()
|
|
143
|
+
for p in self.packages:
|
|
144
|
+
pid, version = js_string(p.result.manifest.get("id")), js_string(p.result.manifest.get("version"))
|
|
145
|
+
if head not in (pid, f"{pid}@{version}"):
|
|
146
|
+
continue
|
|
147
|
+
node = next((n for n in p.result.nodes if n.path == path), None)
|
|
148
|
+
if node is None:
|
|
149
|
+
continue
|
|
150
|
+
rep = pick_representation(node, locale)
|
|
151
|
+
return citation_for(p.result, node, rep, self.clock(), index, rep.body if include_text else None)
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
def search(
|
|
155
|
+
self,
|
|
156
|
+
query: str,
|
|
157
|
+
*,
|
|
158
|
+
limit: int = 5,
|
|
159
|
+
include_all: bool = False,
|
|
160
|
+
locale: str | None = None,
|
|
161
|
+
concepts: list[str] | None = None,
|
|
162
|
+
scope: str | None = None,
|
|
163
|
+
audiences: list[str] | None = None,
|
|
164
|
+
allow_unlabelled: bool = False,
|
|
165
|
+
) -> list[CitationRecord]:
|
|
166
|
+
"""Lexical search over the loaded packages; a shortcut for ``Search(library, LexicalBackend(library))``."""
|
|
167
|
+
from .backends.lexical import LexicalBackend
|
|
168
|
+
from .search import Search
|
|
169
|
+
|
|
170
|
+
return Search(self, LexicalBackend(self), audiences=audiences, allow_unlabelled=allow_unlabelled).search(
|
|
171
|
+
query, limit=limit, include_all=include_all, locale=locale, concepts=concepts, scope=scope
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _key(result: PackageResult) -> str:
|
|
176
|
+
return f"{js_string(result.manifest.get('id'))}@{js_string(result.manifest.get('version'))}"
|
moca_reader/manifest.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Manifest checks beyond the JSON Schema (package spec §4)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ._js import get, parse_date, truthy
|
|
10
|
+
from .diagnostics import Diagnostics
|
|
11
|
+
from .schemas import validate_against
|
|
12
|
+
|
|
13
|
+
MANIFEST_KEYS = {
|
|
14
|
+
"$schema",
|
|
15
|
+
"mocaVersion",
|
|
16
|
+
"id",
|
|
17
|
+
"version",
|
|
18
|
+
"title",
|
|
19
|
+
"description",
|
|
20
|
+
"language",
|
|
21
|
+
"locales",
|
|
22
|
+
"license",
|
|
23
|
+
"publisher",
|
|
24
|
+
"validFrom",
|
|
25
|
+
"validUntil",
|
|
26
|
+
"members",
|
|
27
|
+
"relations",
|
|
28
|
+
"profiles",
|
|
29
|
+
}
|
|
30
|
+
# URN namespaces registered with IANA that are commonly used as package ids. Not exhaustive: M007 is informational.
|
|
31
|
+
# Package spec §4.2: the IANA formal URN namespaces, as in moca-spec data/urn-namespaces.json (a test keeps
|
|
32
|
+
# them equal). Informal namespaces, urn-<number>, are registered too.
|
|
33
|
+
REGISTERED_URN_NIDS = frozenset(
|
|
34
|
+
{
|
|
35
|
+
"3gpp",
|
|
36
|
+
"3gpp2",
|
|
37
|
+
"adid",
|
|
38
|
+
"alert",
|
|
39
|
+
"bbf",
|
|
40
|
+
"broadband-forum-org",
|
|
41
|
+
"c2pa",
|
|
42
|
+
"cablelabs",
|
|
43
|
+
"ccsds",
|
|
44
|
+
"cdx",
|
|
45
|
+
"cgi",
|
|
46
|
+
"clei",
|
|
47
|
+
"csa",
|
|
48
|
+
"cta",
|
|
49
|
+
"ddi",
|
|
50
|
+
"dev",
|
|
51
|
+
"dgiwg",
|
|
52
|
+
"dnp",
|
|
53
|
+
"doi",
|
|
54
|
+
"dslforum-org",
|
|
55
|
+
"dvb",
|
|
56
|
+
"ebu",
|
|
57
|
+
"eic",
|
|
58
|
+
"eidr",
|
|
59
|
+
"epc",
|
|
60
|
+
"epcglobal",
|
|
61
|
+
"etsi",
|
|
62
|
+
"eurosystem",
|
|
63
|
+
"example",
|
|
64
|
+
"fdc",
|
|
65
|
+
"fipa",
|
|
66
|
+
"gdr",
|
|
67
|
+
"gdst",
|
|
68
|
+
"geant",
|
|
69
|
+
"globus",
|
|
70
|
+
"gs1",
|
|
71
|
+
"gsma",
|
|
72
|
+
"gvat",
|
|
73
|
+
"hbbtv",
|
|
74
|
+
"ieee",
|
|
75
|
+
"ietf",
|
|
76
|
+
"iptc",
|
|
77
|
+
"isan",
|
|
78
|
+
"isbn",
|
|
79
|
+
"isni",
|
|
80
|
+
"iso",
|
|
81
|
+
"issn",
|
|
82
|
+
"itu",
|
|
83
|
+
"ivis",
|
|
84
|
+
"knx",
|
|
85
|
+
"lei",
|
|
86
|
+
"lex",
|
|
87
|
+
"liberty",
|
|
88
|
+
"mace",
|
|
89
|
+
"mef",
|
|
90
|
+
"meta",
|
|
91
|
+
"mpeg",
|
|
92
|
+
"mrn",
|
|
93
|
+
"nan",
|
|
94
|
+
"nato",
|
|
95
|
+
"nbn",
|
|
96
|
+
"nena",
|
|
97
|
+
"newsml",
|
|
98
|
+
"nfc",
|
|
99
|
+
"nfi",
|
|
100
|
+
"nzl",
|
|
101
|
+
"oasis",
|
|
102
|
+
"ogc",
|
|
103
|
+
"ogf",
|
|
104
|
+
"oid",
|
|
105
|
+
"oipf",
|
|
106
|
+
"oma",
|
|
107
|
+
"onem2m",
|
|
108
|
+
"onf",
|
|
109
|
+
"pin",
|
|
110
|
+
"pno",
|
|
111
|
+
"publicid",
|
|
112
|
+
"pwid",
|
|
113
|
+
"reso",
|
|
114
|
+
"s1000d",
|
|
115
|
+
"said",
|
|
116
|
+
"schac",
|
|
117
|
+
"service",
|
|
118
|
+
"smpte",
|
|
119
|
+
"stalwart",
|
|
120
|
+
"swift",
|
|
121
|
+
"thread",
|
|
122
|
+
"trivore",
|
|
123
|
+
"tva",
|
|
124
|
+
"uci",
|
|
125
|
+
"ucode",
|
|
126
|
+
"uic",
|
|
127
|
+
"uuid",
|
|
128
|
+
"web3d",
|
|
129
|
+
"wfa",
|
|
130
|
+
"wmo",
|
|
131
|
+
"xmlorg",
|
|
132
|
+
"xmpp",
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
_INFORMAL_URN_NID = re.compile(r"urn-[0-9]+")
|
|
136
|
+
_VENDOR_KEY = re.compile(r"^x-[a-z0-9]+-[A-Za-z0-9-]+\Z")
|
|
137
|
+
_URN = re.compile(r"^urn:([^:]+):", re.IGNORECASE)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def check_manifest(manifest: Any, diagnostics: Diagnostics, known_profiles: Iterable[str] = ()) -> None:
|
|
141
|
+
file = "moca.json"
|
|
142
|
+
for err in validate_against("manifest", manifest):
|
|
143
|
+
diagnostics.add("M002_MANIFEST_SCHEMA", err, file)
|
|
144
|
+
if not isinstance(manifest, dict):
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
for key in manifest:
|
|
148
|
+
if key not in MANIFEST_KEYS and not _VENDOR_KEY.match(key):
|
|
149
|
+
diagnostics.add(
|
|
150
|
+
"M004_UNKNOWN_KEY",
|
|
151
|
+
f'"{key}" is not a MOCA manifest key; it is ignored. Vendor keys use x-<vendor>-<key>.',
|
|
152
|
+
file,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
seen: list[Any] = []
|
|
156
|
+
members = manifest.get("members")
|
|
157
|
+
for m in members if isinstance(members, list) else []:
|
|
158
|
+
member_id = get(m, "id")
|
|
159
|
+
if not truthy(member_id):
|
|
160
|
+
continue
|
|
161
|
+
if member_id in seen:
|
|
162
|
+
diagnostics.add("M005_DUPLICATE_MEMBER", f"member {member_id} is listed more than once", file)
|
|
163
|
+
seen.append(member_id)
|
|
164
|
+
|
|
165
|
+
start = parse_date(manifest.get("validFrom"))
|
|
166
|
+
until = parse_date(manifest.get("validUntil"))
|
|
167
|
+
if start is not None and until is not None and until <= start:
|
|
168
|
+
diagnostics.add("M006_VALIDITY_WINDOW_INVALID", "validUntil must be later than validFrom", file)
|
|
169
|
+
|
|
170
|
+
urn = _URN.match(manifest["id"]) if isinstance(manifest.get("id"), str) else None
|
|
171
|
+
if urn and urn[1].lower() not in REGISTERED_URN_NIDS and not _INFORMAL_URN_NID.fullmatch(urn[1].lower()):
|
|
172
|
+
diagnostics.add(
|
|
173
|
+
"M007_UNREGISTERED_URN",
|
|
174
|
+
f'"urn:{urn[1]}:" is not a registered URN namespace (RFC 8141); prefer an https: or tag: URI',
|
|
175
|
+
file,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
known = set(known_profiles)
|
|
179
|
+
profiles = manifest.get("profiles")
|
|
180
|
+
for uri in profiles if isinstance(profiles, dict) else []:
|
|
181
|
+
if uri not in known:
|
|
182
|
+
diagnostics.add(
|
|
183
|
+
"F001_PROFILE_UNRECOGNISED",
|
|
184
|
+
f"profile {uri} is not recognised; its data is preserved but not interpreted",
|
|
185
|
+
file,
|
|
186
|
+
)
|
moca_reader/metadata.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Flat metadata from a citation record, for stores and frameworks that need flat fields.
|
|
2
|
+
|
|
3
|
+
The names are the consuming guide's ("Feeding your own retrieval stack", moca-spec docs/guides/consuming.md),
|
|
4
|
+
which the Reader interface §7 requires a binding to use when its framework needs flat metadata. Values are
|
|
5
|
+
strings, numbers, booleans or lists of strings; a field with no value is left out, since many stores refuse null.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .citation import CitationRecord
|
|
13
|
+
|
|
14
|
+
FLAT_FIELDS = (
|
|
15
|
+
"moca_package_id",
|
|
16
|
+
"moca_package_version",
|
|
17
|
+
"moca_package_digest",
|
|
18
|
+
"moca_node_id",
|
|
19
|
+
"moca_node_digest",
|
|
20
|
+
"moca_node_ref",
|
|
21
|
+
"moca_status",
|
|
22
|
+
"moca_in_force_until",
|
|
23
|
+
"moca_stale_after",
|
|
24
|
+
"moca_superseded",
|
|
25
|
+
"moca_attested_reviews",
|
|
26
|
+
"moca_signed",
|
|
27
|
+
"moca_audience",
|
|
28
|
+
"moca_concepts",
|
|
29
|
+
"moca_evidence_matched",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def flat_metadata(record: CitationRecord) -> dict[str, Any]:
|
|
34
|
+
"""The consuming guide's ``moca_*`` fields for one citation record.
|
|
35
|
+
|
|
36
|
+
``moca_attested_reviews`` is how many attested reviews the node has; ``moca_evidence_matched`` is true when
|
|
37
|
+
every checked evidence quote was found in its source, false when one was not, and absent when none was checked.
|
|
38
|
+
"""
|
|
39
|
+
package, node, trust = record["package"], record["node"], record["trust"]
|
|
40
|
+
checked = [e["matched"] for e in record.get("evidence", []) if isinstance(e.get("matched"), bool)]
|
|
41
|
+
values: dict[str, Any] = {
|
|
42
|
+
"moca_package_id": package.get("id"),
|
|
43
|
+
"moca_package_version": package.get("version"),
|
|
44
|
+
"moca_package_digest": package.get("digest"),
|
|
45
|
+
"moca_node_id": node.get("id"),
|
|
46
|
+
"moca_node_digest": node.get("digest"),
|
|
47
|
+
"moca_node_ref": node.get("ref"),
|
|
48
|
+
"moca_status": trust.get("status"),
|
|
49
|
+
"moca_in_force_until": trust.get("validUntil"),
|
|
50
|
+
"moca_stale_after": trust.get("staleAfter"),
|
|
51
|
+
"moca_superseded": trust.get("superseded"),
|
|
52
|
+
"moca_attested_reviews": len(trust.get("attestedReviews", [])),
|
|
53
|
+
"moca_signed": package.get("signed"),
|
|
54
|
+
"moca_audience": record.get("audience"),
|
|
55
|
+
"moca_concepts": list(record["concepts"]) if record.get("concepts") else None,
|
|
56
|
+
"moca_evidence_matched": all(checked) if checked else None,
|
|
57
|
+
}
|
|
58
|
+
return {k: v for k, v in values.items() if v is not None}
|
moca_reader/profiles.py
ADDED
moca_reader/py.typed
ADDED
|
File without changes
|