Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,19 @@ jobs:
# so a change to it fails in CI rather than in an unattended deploy.
test "$(printf '%s' "$line" | awk '{print $1}')" = "shuttle"
test -n "$(printf '%s' "$line" | awk '{print $2}')"

repository-pendant-source:
name: Repository pendant source controls
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.11"
- run: python -m pip install 'rdflib==7.6.0'
- run: |
python scripts/test-repository-knowledge.py
python scripts/test-repository-share-publication.py
python scripts/test-repository-share-query.py
38 changes: 38 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,41 @@ jobs:
--notes-file "$RUNNER_TEMP/notes.md" \
dist/shuttle-*.whl dist/shuttle-*.tar.gz dist/checksums.txt
fi

repository-pendant:
name: Verify repository pendant
if: vars.SHUTTLE_PENDANT_PUBLICATION == 'enabled'
needs: release
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
env:
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
ref: refs/tags/${{ inputs.tag || github.ref_name }}
fetch-depth: 0
- name: Verify the bounded public pointer corpus
run: python3 scripts/verify-repository-share.py "$RELEASE_TAG" "$(git rev-parse HEAD)"

repository-pendant-admission:
name: Record repository pendant admission
needs: release
runs-on: ubuntu-latest
permissions:
contents: read
env:
PENDANT_PUBLICATION: ${{ vars.SHUTTLE_PENDANT_PUBLICATION }}
steps:
- name: Record the publication decision
run: |
set -euo pipefail
if [ "$PENDANT_PUBLICATION" = enabled ]; then
state="ADMITTED: pendant verification remains required"
else
state="NOT ADMITTED: no trusted Shuttle pendant publisher is enabled"
fi
printf '%s\n' "$state" | tee -a "$GITHUB_STEP_SUMMARY"
40 changes: 40 additions & 0 deletions docs/knowledge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Repository pendant source

The repository-owned release job exports code and document **pointers**, plus
reviewed summaries of ideas, interfaces, decisions, freshness and starting points.
It does not export source chunk text or line spans. Source witnesses bind each
summary to its defining tracked file. The native index must resolve every
curated pointer before the build can proceed. Stored queries and validating
shapes travel with the share.

`alignments.ttl` is currently a valid empty SSSOM/RDF carrier. Its accompanying
status file explicitly records zero approved mappings and seals those bytes.
An engine advertising `share --alignments` carries this reviewed file physically,
with its exact manifest filename and SHA-256 binding. Older engines retain the
inline empty carrier without adding an undeclared archive member.
This is not evidence of global absence. Missing, changed or malformed input
refuses; an explicit empty carrier does not. Dependencies and glossary references
are not identity alignments. Code/commit deduplication and unrelated exactMatch
records are not cross-repository mapping approval.

Candidate mappings must go through Quipu's existing `align propose` and
`align decide` curation path. Keep their source graphs and source witnesses;
do not fill an approval author from a lexical score. Only the reviewed mapping
record may produce derived identity through the engine's existing apply path.
The current source profile deliberately does not import arbitrary nonempty
mapping data until an actual curated artifact and its transport are reviewed.

The copied release workflow verifies the three public assets after the ordinary
release finishes only when `SHUTTLE_PENDANT_PUBLICATION` is exactly `enabled`.
A separate job records the admission state; `NOT ADMITTED` does not claim
verified assets or enable a producer. The managed trusted host runs this repository's own
publisher, keeps its publication policy private, pins both native engine
executables and the indexer, and refuses retained uncertain upload outcomes.
Default configuration still publishes only the existing Quipu corpus. Enabling
another repository requires its actual trusted host configuration and a clean
release containing this job; committing this source does not publish an asset.

The publisher requires the engine's model/digest/dimension manifest recipe.
Loading and vector rebuilding belong to the native engine. No hosted job gets
private publication policy or a new credential route. These source controls do
not establish public asset availability, live loading or service acceptance.
8 changes: 8 additions & 0 deletions docs/knowledge/alignment-status.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"status": "no-approved-mappings",
"approved_record_count": 0,
"carrier_sha256": "a8f3d420900dce866437e3ecfaba792b4ac4c301ae4702966f9147d4b7d49e3b",
"scope": "scbrown/shuttle repository pendant source",
"basis": "No verified approved artifact or pairs supplied at source review; empty carrier is explicit. This is not a global absence claim.",
"curation_contract": "https://github.com/scbrown/quipu/blob/main/docs/design/cross-graph-alignment.md"
}
6 changes: 6 additions & 0 deletions docs/knowledge/alignments.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# SSSOM/RDF carrier: explicitly no approved Shuttle cross-stack mappings.
# This is an empty mapping graph, not an absent/corrupt input or a claim that
# no mappings exist anywhere. Candidate records require the existing curator
# path before publication as accepted mappings. Dependencies remain dependencies.
@prefix sssom: <https://w3id.org/sssom/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
130 changes: 130 additions & 0 deletions docs/knowledge/repository-knowledge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"source_revision": "33001f173986a0496b3fc9f0afe4ebc658e2618f",
"reviewed_at": "2026-10-21T23:38:05.033622Z",
"max_age_seconds": 2592000,
"records": [
{
"kind": "idea",
"source": "README.md",
"subject": "http://aegis.gastown.local/ontology/doc/shuttle/README.md",
"summary": "Shuttle records workflow transitions in a local append-only JSONL log and exports signed history into governed, time-windowed Quipu graphs.",
"source_sha256": "af53641f5f1d6b5a191f477cd40742cfb5ef5e8d2a37ffe4dc2af34563d3d82f",
"type": "Concept"
},
{
"kind": "interface",
"source": "shuttle/cli.py",
"subject": "http://aegis.gastown.local/ontology/code/shuttle/shuttle%2Fcli.py",
"summary": "The CLI defines and advances workflows, exports history, verifies signatures and freezes windows. It has no HTTP or MCP server.",
"source_sha256": "c4255c23dc66050d2a8f8e928123db08ae9b7d1dddcb27018db464b8efbc6931",
"properties": {
"interfaceKind": "CLI",
"publicName": "shuttle"
}
},
{
"kind": "interface",
"source": "shuttle/state.py",
"subject": "http://aegis.gastown.local/ontology/code/shuttle/shuttle%2Fstate.py",
"summary": "SHUTTLE_STATE_DIR selects local append-only workflow state; otherwise the user state directory is used.",
"source_sha256": "d1f0b8428753c569725b925d22e08d25d8590dc1d2a9f2106d172c7d44b74a5b",
"properties": {
"interfaceKind": "config",
"publicName": "SHUTTLE_STATE_DIR"
}
},
{
"kind": "interface",
"source": "shuttle/signing.py",
"subject": "http://aegis.gastown.local/ontology/code/shuttle/shuttle%2Fsigning.py",
"summary": "SHUTTLE_KEY_DIR selects agent key storage; require-existing-key mode refuses missing or unsafe key files and does not create replacements.",
"source_sha256": "4e9a268d068030989e5131bb8d4d9e83e671b3c9e497aaa8fbd10449acc246e3",
"properties": {
"interfaceKind": "config",
"publicName": "SHUTTLE_KEY_DIR / existing_keys_only"
}
},
{
"kind": "interface",
"source": "shuttle/model.py",
"subject": "http://aegis.gastown.local/ontology/code/shuttle/shuttle%2Fmodel.py",
"summary": "Workflow definitions and transition events are pure data models; replay folds validated transitions to reconstruct state.",
"source_sha256": "b6be4c052d4abda4965815da0e9a44d85dc19df2224e8cc29e1bb117f5126198",
"properties": {
"interfaceKind": "file-format",
"publicName": "Workflow definition JSON and transition records"
}
},
{
"kind": "interface",
"source": "shuttle/quipu_client.py",
"subject": "http://aegis.gastown.local/ontology/code/shuttle/shuttle%2Fquipu_client.py",
"summary": "The client uses Quipu HTTP graph, knot and episode APIs. Capability and write refusals are distinct from empty results; it does not serve REST itself.",
"source_sha256": "9037e275caf804b0e9a5a0234631d92f85558b1c5cb7c9f09e2a5b541f622df7",
"properties": {
"interfaceKind": "library",
"publicName": "Quipu client"
},
"depends_on": [
"http://aegis.gastown.local/ontology/repo_quipu"
]
},
{
"kind": "interface",
"source": "shuttle/seeds.py",
"subject": "http://aegis.gastown.local/ontology/code/shuttle/shuttle%2Fseeds.py",
"summary": "Seed steps create and reconcile linked work through the configured sd executable; backend routing belongs to that client.",
"source_sha256": "b40c0c128697d92bcfcf326768fab2c9e545b8a291618f2362d068e2c9bbc7f6",
"properties": {
"interfaceKind": "library",
"publicName": "Seed-step reconciliation"
},
"depends_on": [
"http://aegis.gastown.local/ontology/repo_seeds"
]
},
{
"kind": "decision",
"source": "docs/design/shuttle.md",
"subject": "http://aegis.gastown.local/ontology/doc/shuttle/docs%2Fdesign%2Fshuttle.md",
"summary": "The v1 design deliberately exposes a CLI and library rather than a daemon, HTTP or MCP server. Consumers read the governed graph instead.",
"source_sha256": "959dd5719f22d0997e7438520eccacc3907fa260fa904f14903d2954a85ad166",
"type": "DecisionRecord",
"properties": {
"status": "active",
"supersessionStatus": "not-recorded-in-source"
}
},
{
"kind": "start-here",
"source": "README.md",
"subject": "http://aegis.gastown.local/ontology/doc/shuttle/README.md",
"summary": "Begin with the workflow quick start and role in the stack; registration and production writes remain explicit operator actions.",
"source_sha256": "af53641f5f1d6b5a191f477cd40742cfb5ef5e8d2a37ffe4dc2af34563d3d82f",
"properties": {
"startHereOrder": 1,
"glossaryTerm": "https://scbrown.github.io/quechua/ns#Decision"
}
},
{
"kind": "start-here",
"source": "docs/operating.md",
"subject": "http://aegis.gastown.local/ontology/doc/shuttle/docs%2Foperating.md",
"summary": "Read the operational command and environment contract before exporting or freezing workflow history.",
"source_sha256": "d5634302171e1787507a4e065b46126fd4e3daa328954209981719817382bc64",
"properties": {
"startHereOrder": 2
}
},
{
"kind": "start-here",
"source": "docs/seed-steps.md",
"subject": "http://aegis.gastown.local/ontology/doc/shuttle/docs%2Fseed-steps.md",
"summary": "Read seed-step visit, replay and outcome semantics before connecting work-item completion to workflow transitions.",
"source_sha256": "af2b562361814de135058f64047c0a36d0e8b40ec490d2bc26fd1b6374071ed9",
"properties": {
"startHereOrder": 3
}
}
]
}
62 changes: 62 additions & 0 deletions docs/knowledge/repository-knowledge.shapes.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@prefix a: <http://aegis.gastown.local/ontology/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix sssom: <https://w3id.org/sssom/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix prov: <http://www.w3.org/ns/prov#> .

# This profile constrains only explicitly curated repository knowledge. It
# does not turn every imported Concept or CodeModule into a Bobbin record.
a:ShuttleKnowledgeProfile a sh:NodeShape ;
sh:targetSubjectsOf a:knowledgeKind ;
sh:property [ sh:path rdfs:label ; sh:minCount 1 ; ] ;
sh:property [ sh:path rdfs:comment ; sh:minCount 1 ; sh:datatype xsd:string ; ] ;
sh:property [ sh:path a:knowledgeKind ; sh:minCount 1 ;
sh:in ( "idea" "interface" "decision" "start-here" ) ; ] ;
sh:property [ sh:path a:filePath ; sh:minCount 1 ; sh:datatype xsd:string ; ] ;
sh:property [ sh:path a:reviewAfter ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:dateTime ; ] ;
sh:property [ sh:path a:sourceHash ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9a-f]{64}$" ; ] ;
# maxAge is seconds since the explicit source review, not an eviction rule.
sh:property [ sh:path a:maxAge ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:integer ; sh:minExclusive 0 ; ] ;
sh:property [ sh:path prov:wasDerivedFrom ; sh:minCount 1 ; sh:nodeKind sh:IRI ; ] .

# Select on the stable record discriminator, never on a field whose absence
# this profile must detect. The first branch admits the other known kinds;
# the second enforces this kind's metadata, even when its selector is deleted.
a:ShuttleInterfaceProfile a sh:NodeShape ;
sh:targetSubjectsOf a:knowledgeKind ;
sh:or (
[ sh:property [ sh:path a:knowledgeKind ; sh:minCount 1 ;
sh:in ( "idea" "decision" "start-here" ) ; ] ]
[ sh:property [ sh:path a:interfaceKind ; sh:minCount 1 ; sh:maxCount 1 ;
sh:in ( "CLI" "MCP" "REST" "config" "file-format" "library" ) ; ] ;
sh:property [ sh:path a:publicName ; sh:minCount 1 ; sh:datatype xsd:string ; ] ;
sh:property [ sh:path a:depends_on ; sh:nodeKind sh:IRI ; ] ]
) .

a:ShuttleDecisionProfile a sh:NodeShape ;
sh:targetSubjectsOf a:knowledgeKind ;
sh:or (
[ sh:property [ sh:path a:knowledgeKind ; sh:minCount 1 ;
sh:in ( "idea" "interface" "start-here" ) ; ] ]
[ sh:property [ sh:path a:status ; sh:minCount 1 ; sh:maxCount 1 ;
sh:in ( "active" "superseded" "proposed" ) ; ] ;
sh:property [ sh:path a:supersessionStatus ; sh:minCount 1 ; sh:maxCount 1 ;
sh:in ( "not-recorded-in-source" "recorded" ) ; ] ]
) .

# Existing SSSOM/RDF is a record, not a second identity-assertion format. The
# native align decide/apply path remains the only way to derive identity knots.
a:ShuttleMappingRecordProfile a sh:NodeShape ;
sh:targetSubjectsOf sssom:subject_id ;
sh:property [ sh:path rdfs:label ; sh:minCount 1 ; ] ;
sh:property [ sh:path sssom:subject_id ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; ] ;
sh:property [ sh:path sssom:object_id ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; ] ;
sh:property [ sh:path sssom:predicate_id ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; ] ;
sh:property [ sh:path sssom:mapping_justification ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; ] .

a:ShuttleNoHandAuthoredIdentity a sh:NodeShape ;
sh:targetSubjectsOf owl:sameAs ;
sh:property [ sh:path rdfs:label ; sh:minCount 1 ; ] ;
sh:property [ sh:path owl:sameAs ; sh:maxCount 0 ; ] .
32 changes: 32 additions & 0 deletions docs/knowledge/stored-queries.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"name": "repository_ideas",
"description": "Repository-owned repository ideas",
"template": "PREFIX a: <http://aegis.gastown.local/ontology/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT * WHERE { ?s a a:Concept; rdfs:comment ?summary . FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/shuttle/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/shuttle/\")) } ORDER BY ?s LIMIT 100",
"params": []
},
{
"name": "repository_interfaces",
"description": "Repository-owned repository interfaces",
"template": "PREFIX a: <http://aegis.gastown.local/ontology/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT * WHERE { ?s a:interfaceKind ?kind; a:publicName ?name . FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/shuttle/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/shuttle/\")) } ORDER BY ?s LIMIT 100",
"params": []
},
{
"name": "repository_decisions",
"description": "Repository-owned repository decisions",
"template": "PREFIX a: <http://aegis.gastown.local/ontology/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT * WHERE { ?s a a:DecisionRecord; a:status ?status . OPTIONAL { ?s <http://purl.org/dc/terms/isReplacedBy> ?successor } FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/shuttle/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/shuttle/\")) } ORDER BY ?s LIMIT 100",
"params": []
},
{
"name": "repository_start_here",
"description": "Repository-owned repository start here",
"template": "PREFIX a: <http://aegis.gastown.local/ontology/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT * WHERE { ?s a:startHereOrder ?order . FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/shuttle/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/shuttle/\")) } ORDER BY ?s LIMIT 100",
"params": []
},
{
"name": "repository_freshness",
"description": "Repository-owned repository freshness",
"template": "PREFIX a: <http://aegis.gastown.local/ontology/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT * WHERE { ?s a:reviewAfter ?reviewAfter; a:maxAge ?maxAgeSeconds . FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/shuttle/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/shuttle/\")) } ORDER BY ?s LIMIT 100",
"params": []
}
]
Loading
Loading