Skip to content

Let one person configure several Companies, chosen per repository - #30

Open
MrJoy wants to merge 17 commits into
wandercom:mainfrom
MrJoy:feat/multiple-companies
Open

MrJoy wants to merge 17 commits into
wandercom:mainfrom
MrJoy:feat/multiple-companies

Conversation

@MrJoy

@MrJoy MrJoy commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Why

A person who works for several organizations needs each to be its own Company: its own root key, facts, authorities and certificates. The launcher config admits one [company]. Merging organizations into one Company sends org A's rulings into org B's coding sessions. Swapping XDG_CONFIG_HOME per invocation doesn't work either, because the installed hooks run kinbase hooks dispatch <host> <event> with no arguments.

What

This PR adds a named form, [companies.<name>], which can't be combined with [company]. The single form is unchanged byte for byte, and the existing suite passes untouched.

[companies.wander]
url = "http://127.0.0.1:8421"
facts_token_file = "…/wander/facts.token"
root_public_key_file = "…/wander/company-root.pub"
cache_root = "…/wander/cache"
discovery_hints = ["github.com/wandercom/*"]

[companies.acme]
url = "http://127.0.0.1:8422"
…
discovery_hints = ["github.com/acme-corp/*", "gitlab.acme.example/platform/**"]

The launcher picks at most one Company per invocation, from the repository the invocation acts on: --repo, the hook envelope cwd, or else the process cwd. From that Company it builds today's single-Company config, so build_trust, the caches, the projector and the hooks still see exactly one Company. The ratified "one externally configured Company root" therefore holds per repository and per invocation.

Selection only narrows. It looks at three kinds of evidence, from configured Companies only:

  • holds: the Company's cache keeps a certificate for the worktree UUID, and it verifies against that Company's root;
  • hint match: the Company's discovery_hints match the repository's normalized origin;
  • pin claim: the Company's cache already pins that origin.

The outcome:

  • One Company in the union is selected.
  • None means Codebase-only mode.
  • Two or more, or any evidence that can't be read, is a refusal: CONFIG_INVARIANT with detail.reason.

No kind of evidence outranks another. The UUID comes from worktree bytes and a certificate binds no origin, so a copied or forked .kin/ must not carry one organization's certificate into another organization's repository.

Also in this PR

  • Session candidates are bound to where they were minted. Under the named form, a Company candidate is company:<company_id> and records its repository_uuid. A checkpoint delivers it only from that repository with that Company selected; otherwise the candidate is held. Before this, candidates recorded only company:root and were admitted against the process cwd. A session that stopped in another organization's repository then delivered to that organization's Company. The probe for this fails when the guard is removed.
  • Hooks never block. When selection is refused or finds nothing, SessionStart gets a constant notice in the model context. On Claude it also gets a constant systemMessage that the person sees. Hook output never contains another Company's name, URL, cache path or company_id; a canary probe checks this.
  • Conversion from [company] is safe. If the per-Company default key would be minted next to the single form's existing key, the command refuses instead. To use one maintainer key for several Companies, set [identity] share_maintainer_key = true.
  • Revocation watermarks are keyed by company_id under the named form. A converted config carries its unqualified value over to one Company only.
  • Unselected repositories get their own local Company store, per repository, instead of one shared fallback.
  • project --evidence-repo refuses an evidence repository that resolves to a different Company.
  • status and doctor --repo show company_selection: the configured Companies, the evidence, the outcome and a remediation. This is terminal output only.
  • repo issue --company <name> takes a Company name under the named form. It refuses certified-elsewhere when another configured Company claims the repository, and the remediation gives the new-identity procedure: delete .kin/kinbase.toml, then run repo issue and repo init.

Spec

The error taxonomy is unchanged. spec/amendment-004-multiple-companies.md is a candidate that states the rule, adds the named form to cli.md, and lists the limitations and follow-ups:

  • certified-elsewhere is client-side only; server-side mint-only issuance is a follow-up.
  • Personal-store keys outlive a cache purge; company forget is a follow-up.
  • Cross-Company moves that keep signed history are a follow-up.

The amendment carries no authority until ratified. The code is meant to stand on today's ratified text.

How it was designed

  1. The problem was put through a constrain interview (wandercom/constrain). The challenge phase settled what counts as "holding" a certificate and withdrew a proposed diagnostics ledger.

  2. The resulting design was reviewed by the seven Advocate personas: Red Team, Adversarial, Sage, User, SME, Good Friend and Pat Helland. They found two defects in the first draft, and both are fixed here:

    • Cross-Company candidate delivery. Red Team, Adversarial and Helland each found it independently.
    • Worktree UUID outranking the origin. Red Team, Adversarial, SME and Helland flagged it.

    They also prompted the key-migration guard, the refusal on unreadable evidence, the per-repository fallback store, the watermark carry-over and the human-visible notice.

Tests

crates/kinbase/tests/multiple_companies.rs runs two real company serve processes against an isolated HOME, XDG directories and Personal root:

  • each repository is served by its own Company, including from the hook envelope cwd;
  • a fork carrying another Company's UUID is refused, hooks exit 0 with no Company names or URLs, and the new-identity procedure recovers;
  • two Companies holding one UUID, forced by posting to the second server directly, is refused;
  • unreadable evidence refuses instead of falling through;
  • a repository no Company claims runs Codebase-only and the hook says so;
  • converting a config refuses until the legacy key is named;
  • sharing a maintainer key must be stated;
  • malformed named configs are refused;
  • a Company candidate minted in A's repository never reaches B's store.

There are also unit tests for hint normalization and matching (* and **) and for whole-token scrubbing.

cargo test: 267 passed, 0 failed (main: 245). Two probes were also checked against mutants of the code they guard: the candidate-binding guard removed, and "held certificate wins" restored. Both probes fail against those mutants.

Review round 1 (5662d0d): every finding from Copilot and Adapt was reproduced by a probe that fails on 3ea19f2 before being fixed. The exception is the repo init unreadable-root ordering, which was fixed by reading. Details are in each thread.

Also on this branch, found while setting up three real organizations:

  • 175259a: repositories with no origin URL were all answered with the first such repository's certificate. Fixed in both the client and the service, and covered by a probe.
  • 3ea19f2: the "no Company" notice now appears only in a repository that carries .kin/kinbase.toml, rather than in every directory a host opens.

Not covered by an end-to-end probe: the watermark carry-over (it needs a revocation fixture, so it has a unit test of the attribution instead) and project --evidence-repo across Companies.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RoD6Wz3VRydW8LgHiWf3KL

A person who works for several organizations needs each to be its own
Company, but the launcher config admitted one [company]. Merging them sent
one organization's rulings into another's sessions; swapping
XDG_CONFIG_HOME per invocation was invisible to the installed hooks, which
run `kinbase hooks dispatch <host> <event>` with no arguments.

Add a named form, [companies.<name>], mutually exclusive with [company]
(which is unchanged). The launcher selects at most one Company per
invocation from the repository it acts on (--repo, the hook envelope cwd,
else the process cwd) and derives today's single-Company configuration
from it, so build_trust, caches, projector and hooks still see one
Company. Selection only narrows: the evidence is which configured
Companies hold a verifying certificate for the worktree's UUID, whose
discovery_hints match the normalized origin, and whose caches pin that
origin; exactly one is selected, none is Codebase-only, and several, or
any that cannot be read, is CONFIG_INVARIANT with a detail.reason. No
evidence outranks another, because the UUID is worktree bytes and a
certificate binds no origin: a copied .kin/ must not carry one
organization's certificate into another's repository.

Company candidates minted under the named form are bound to the selected
company_id and their repository UUID, and a checkpoint delivers them only
from that repository with that Company selected. Before this, candidates
recorded only company:root and were admitted against the process cwd, so
a session that stopped in another organization's repository delivered to
that organization's Company.

Also: revocation watermarks are keyed by company_id (carrying a converted
config's unqualified sighting to one Company), unselected repositories
get per-repository local Company stores, converting [company] refuses
rather than minting new keys, sharing one maintainer key needs
[identity] share_maintainer_key, hook output never names another Company
and tells the person (systemMessage) when no Company was chosen, and
status/doctor report the selection evidence.

The error taxonomy is unchanged. spec/amendment-004-multiple-companies.md
is a candidate stating the rule, the named form for cli.md, and the
limitations; it is not authority until ratified.

Spec: architecture.md "Trust and key lifecycle" (one Company root per repository and invocation); cli.md "Configuration"
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoD6Wz3VRydW8LgHiWf3KL
Copilot AI balanced review requested due to automatic review settings October 9, 2026 23:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Selection ordering, watermark concurrency, and evidence handling contain unresolved correctness issues.

5 open findings
What changed in this PR

Adds per-repository selection among multiple named Companies while preserving downstream single-Company behavior.

Changes:

  • Adds named Company configuration and evidence-based selection.
  • Binds candidates, stores, watermarks, and projections to the selected Company.
  • Adds hook safeguards, diagnostics, and multi-Company integration tests.
File Description
spec/​amendment-004-multiple-companies.md Defines the candidate multi-Company contract.
crates/​kinbase/​tests/​multiple_companies.rs Adds end-to-end multi-Company probes.
crates/​kinbase/​src/​store.rs Separates unselected repository stores.
crates/​kinbase/​src/​session.rs Binds Company candidates to Company and repository.
crates/​kinbase/​src/​selection.rs Implements Company selection and diagnostics.
crates/​kinbase/​src/​repository.rs Scopes watermarks and exposes selection status.
crates/​kinbase/​src/​lib.rs Exports the selection module.
crates/​kinbase/​src/​launcher.rs Applies selection when constructing launcher state.
crates/​kinbase/​src/​hooks.rs Selects from hook cwd and scrubs output.
crates/​kinbase/​src/​corpus.rs Recognizes named Company destinations.
crates/​kinbase/​src/​config.rs Parses and validates named Companies.
crates/​kinbase/​src/​cli.rs Selects command targets and checks projection evidence.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/kinbase/src/config.rs
Comment thread crates/kinbase/src/repository.rs Outdated
Comment thread crates/kinbase/src/cli.rs Outdated
Comment thread crates/kinbase/src/hooks.rs
Comment thread crates/kinbase/src/selection.rs

@adaptcom adaptcom Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confidence Score: 1/5

Summary

Adds repository-specific Company selection, but reproduced selection, redaction, checkpoint, and revocation defects make this unsafe to merge. Jeremy McEntire’s human review is required after fixes.

Important Files Changed

File Overview
crates/kinbase/src/cli.rs Repository-target routing and cross-Company projection checks.
crates/kinbase/src/config.rs Named configuration, identity migration, and premature Company selection.
crates/kinbase/src/corpus.rs Recognizes Company-qualified candidate destinations.
crates/kinbase/src/hooks.rs Envelope routing and redaction introduce output and checkpoint regressions.
crates/kinbase/src/launcher.rs Selected Company access, diagnostics, and redaction helpers.
crates/kinbase/src/lib.rs Exports the Company selection module.
crates/kinbase/src/repository.rs Revocation watermark migration misattributes ownership and races.
crates/kinbase/src/selection.rs Combines repository claims but mistakes inaccessible caches for absent evidence.
crates/kinbase/src/session.rs Binds Company candidates to repositories without aligning hook checkpoint paths.
crates/kinbase/src/store.rs Isolates unselected Company fallback stores by repository.
crates/kinbase/tests/multiple_companies.rs Misses reproduced permissions, redaction, hook-routing, and watermark boundary failures.
spec/amendment-004-multiple-companies.md Candidate multi-Company contract includes unsafe first-claimant watermark migration.

↻ Re-run review · View in Adapt

Comment thread crates/kinbase/src/selection.rs Outdated
Comment thread crates/kinbase/src/hooks.rs Outdated
Comment thread crates/kinbase/src/hooks.rs Outdated
Comment thread crates/kinbase/src/config.rs
Comment thread crates/kinbase/src/session.rs
Comment thread crates/kinbase/src/repository.rs Outdated
Comment thread crates/kinbase/src/repository.rs Outdated
MrJoy and others added 3 commits October 9, 2026 16:25
`repo issue` sent `discovery_hint: ""` for a repository with no origin
remote, and the service matched certificates by hint, so the second such
repository was answered with the first one's certificate: two
repositories shared one UUID and each other's facts. The client now omits
an empty hint and the service ignores one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoD6Wz3VRydW8LgHiWf3KL
With the hooks installed for every project, SessionStart told the person
"Kinbase found no Company" in every directory no configured Company
claims, home and scratch directories included, so the notice that marks a
real misconfiguration was buried by sessions that were never company work.
A refusal still always speaks; an empty selection now speaks only in a
repository that carries .kin/kinbase.toml, which was certified and lost
its Company.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoD6Wz3VRydW8LgHiWf3KL
Each defect below was reproduced by a probe that fails on 3ea19f2 and
passes here.

- A hook read the process cwd's Company before its envelope named the
  repository: `cli` loaded the launcher for every command and `hooks run`
  loaded it again, so a Company with missing credentials where the host
  started failed SessionStart for a healthy one. Hook dispatch now parses
  the envelope and sets its target before any launcher loads.
- Stop and PreCompact admitted candidates against the process cwd while
  the Company was selected from the envelope cwd, holding candidates from
  the very repository the host named. The checkpoint now takes the
  envelope's repository.
- Hook output was scrubbed as text after encoding: identifiers survived in
  the base64 envelope, replacement left the context frame's length stale,
  and a Company named like a JSON literal (`true`) broke the document, so
  the unscrubbed response went out instead. Scrubbing now walks JSON values
  before anything is encoded or framed, and never falls back.
- Evidence lookups used `exists()`, which reads a permission error as
  absence and let another Company's hint win; only NotFound is absence now,
  and `repo init` reports unreadable roots before ambiguity.
- A refused `status` returned before attaching its selection report, the
  case where the evidence is needed; the refusal now carries it.
- Two spellings of one cache root (`a/../cache`) passed the overlap check
  before the directory existed; `.`/`..` are refused and paths compare by
  their deepest existing ancestor.
- The single-form watermark went to whichever Company claimed it first,
  racily and without regard to whose it was. It now carries over only to
  the one Company whose verified snapshot carries that revocation, computed
  rather than claimed; any doubt starts a fresh watermark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoD6Wz3VRydW8LgHiWf3KL

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread crates/kinbase/src/launcher.rs
Comment thread crates/kinbase/src/cli.rs Outdated
Comment thread crates/kinbase/src/cli.rs Outdated
Comment thread crates/kinbase/src/selection.rs Outdated
MrJoy and others added 4 commits October 9, 2026 18:51
`company init` and `company serve` read only their own `--config`, but
`cli` loaded the launcher first, and the launcher derives the selected
Company's access from this user's config. Started inside a repository
whose Company had lost its facts token, `company init` for an unrelated
service failed with "facts token file is unreadable". The service
commands now dispatch before the launcher loads, as hooks do, and keep
its descriptor attestation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUe5FnuCbu7m6K3AsYoM2E
Target selection pre-read the `--certificate` with `std::fs::read`, so a
caller-supplied file of any size was allocated whole before `repo init`'s
64 KiB ceiling could refuse it. It now goes through `read_bounded` with
the same ceiling, and an oversized file still falls back to the
repository target so `repo init` reports it.

`read_bounded` trusted the length from `symlink_metadata`, which is 0 for
a FIFO or a device and stale for a growing file, then read to end of
file. The read itself now stops one byte past the ceiling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUe5FnuCbu7m6K3AsYoM2E
`store_snapshot` writes the snapshot row before its meta rows, so a crash
between them leaves a verified snapshot with no `company_id` row. Under
the named form `company_key_scope` then returned `None`, and
`trust_facts` read that as the single form: it read and wrote the
unqualified revocation watermark that every Company shares.

`cached_company_id` now falls back to the `company_id` in the stored
snapshot, which was signature-checked before it was stored and always
names its Company. When the named form still has no scope, `trust_facts`
reads and writes no watermark at all, so every warranted trace reopens
rather than borrowing another Company's cursor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUe5FnuCbu7m6K3AsYoM2E
A held candidate carries `destination: company:<company_id>`, and the
checkpoint puts it in the hook response. The scrubber learned other
Companies' ids only from their readable caches, so when that cache could
not be read (the `company-unverifiable` case, where hooks continue on
purpose) alpha's id reached beta's Stop output.

`Scrubber` now also rewrites the id of any `company:<id>` that is not
`company:root` and not the selected Company's own, from the shape of the
destination rather than the cache. It is built once per hook response
instead of reopening every Company's cache for each string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUe5FnuCbu7m6K3AsYoM2E

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved repository-isolation and fail-closed selection defects can mix facts across organizational boundaries.

2 open findings
3 resolved since last review
Previously missed (4)

In code that hasn't changed since last review

Medium severity Validate Company names at the CLI boundary

crates/​kinbase/​src/​cli.rs:410

company is a raw command-line string here, while configured Company names are validated identifiers. An argument containing controls or bidi/noncharacter code points reaches selection and is interpolated into the company-unknown error instead of being refused as an invalid identifier. Apply the same Company-name validator at the CLI boundary before constructing Target::Named.

Medium severity Propagate refused evidence repository selection

crates/​kinbase/​src/​cli.rs:467

A refused evidence-repository selection is collapsed to None here. When the primary repository is also unselected, an ambiguous or unreadable evidence repository therefore passes this equality check and its Codebase facts are mixed into the projection, bypassing the fail-closed selection rule. Propagate the evidence repository's refusal before comparing selected Companies.

Medium severity Reserve context space for the required selection notice

crates/​kinbase/​src/​hooks.rs:683

The selection notice is appended after all repository unknowns, but bound_evidence keeps unknowns in order and drops everything after the item/byte ceiling. A repository with enough existing unknowns can therefore lose the required refusal/no-Company notice from the model context. Put this constant notice first (or reserve space for it) so bounding cannot omit it.

Medium severity Route Git subprocesses through World::isolate

crates/​kinbase/​tests/​multiple_companies.rs:224

These Git subprocesses inherit the runner's live HOME and XDG configuration, so repository setup can read global Git config/templates instead of remaining inside the probe's temporary environment. Route the command through World::isolate just like the Kinbase subprocesses.

This issue also appears on line 829 of the same file.

🧠 Review effort: Balanced

Comment thread crates/kinbase/src/store.rs Outdated
MrJoy and others added 5 commits October 9, 2026 19:07
Under several Companies, a repository no Company claims keeps its local
Company answers in a fallback store named by the hash of its canonical
path. Deleting a checkout and cloning another repository at the same path
reused the old store, so `corpus::load_store` read the previous
repository's signed local events and authority answers for the new one.
The namespace now also hashes the certified UUID, when there is one, and
the device and inode of the Git common directory, which a new clone
replaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUe5FnuCbu7m6K3AsYoM2E
`same_company_evidence` compared the selected Company of each evidence
repository with this repository's, reading a refusal as "no Company". In
a repository no Company claims, an ambiguous or unverifiable evidence
repository compared equal and its Codebase facts went into the
projection. The evidence repository's refusal now stops the command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUe5FnuCbu7m6K3AsYoM2E
Under several Companies `repo issue --company` names a configured
Company, and an unknown name went into the `company-unknown` message as
given, control and bidi characters included. A name that fails the same
validator configured names pass is now refused without being echoed.
The check lives in selection rather than clap because under the single
form `--company` is the service URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUe5FnuCbu7m6K3AsYoM2E
SessionStart appended the refusal or no-Company notice after the
repository's open unknowns, and `bound_evidence` keeps unknowns in order
and drops what follows the 32-item or byte ceiling. A repository with
enough unknowns lost the notice from the model's context. It now goes
first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUe5FnuCbu7m6K3AsYoM2E
`World::repo` and the no-origin probe ran `git` with the runner's live
`HOME` and XDG directories, so a global Git config or template could
shape the repositories the probes built. They now go through
`World::isolate`, as the kinbase subprocesses do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUe5FnuCbu7m6K3AsYoM2E
@MrJoy

MrJoy commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

The four "Previously missed" findings from Copilot's review of a79f878 have no threads, so they're answered here. Each one reproduced, and each fix is pushed (head f4b389e).

Validate Company names at the CLI boundary (cli.rs:410)

Reproduced: repo issue --company $'evil\e[2J‮' printed the raw argument in the company-unknown message (probe fails on a79f878). Fixed in ba7772f. A name that fails config::valid_company_name, the same check configured names pass, is now refused without being echoed. The check sits in selection::resolve and not in clap, because under the single form --company is the service URL. Probe: a_company_name_from_the_command_line_is_validated_before_it_is_echoed.

Propagate refused evidence repository selection (cli.rs:467)

Reproduced: from a repository no Company claims, project --evidence-repo <ambiguous fork> projected instead of refusing (probe fails on a79f878). Fixed in 1888cef. same_company_evidence returns the evidence repository's refusal before comparing selected Companies. Probe: a_refused_evidence_repository_is_refused_even_beside_an_unselected_one.

Reserve context space for the required selection notice (hooks.rs:683)

Reproduced: with 40 open unknowns, bound_evidence dropped the notice (unit test fails on a79f878). Fixed in 35aec6e. The notice now goes first among the unknowns. Test: hooks::tests::the_selection_notice_survives_the_projection_ceiling.

Route Git subprocesses through World::isolate (multiple_companies.rs:224, :829)

Fixed in f4b389e. Both git invocations now go through World::isolate. This is test hygiene, so there's no probe for it.

MrJoy and others added 2 commits October 14, 2026 10:20
hooks.rs: the launcher now loads inside dispatch_event, after the
Company target is set, so the secret mask table is read after that
load. Hook output is masked for known secrets, then scrubbed of other
Companies' destinations.

The amendment-004 example named a real deployment; main's
generic_by_construction probe refused it, so it now uses globex.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UL8AgvtwuWA1D8sDqxTd1J
Brings in wandercom#31.  `checkpoint_internal` now resolves its `repo` argument
once, above the admission loop, because wandercom#31 uses it for the batched
receipt sync and the checkpoint append.  `company_binding_holds` moves
from `admit_candidate` into `admit_unsettled`: a settled receipt delivers
nothing, and every path that can deliver one goes through
`admit_unsettled`, the checkpoint loop included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DQZ8FhUz5avxQma8Bjyrrv

@blast-hardcheese blast-hardcheese left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced on e025856 with two local Company services and a repository whose origin matches Company A's hint. repo issue --company B returns issued, but installing that exact certificate with repo init exits 4 (company-ambiguous). This is one issuance/install failure mode: the command hands back a certificate that the normal next step rejects. Please make explicit issuance and installation agree about foreign hints, either by refusing issuance up front or by accepting the certificate under an intentional precedence rule.

Comment thread crates/kinbase/src/selection.rs

@blast-hardcheese blast-hardcheese left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced separately on e025856 with two signed local Company services and 50 Personal observations. The old single-Company watermark for a key at revocation cursor 1001 is 42 and belongs to B. Both services have revoked that key at cursor 1001, but B's local signed snapshot is still the pre-revocation snapshot when A first reads it. A therefore migrates the legacy watermark to its own scoped key as 42. After B refreshes its signed snapshot, both Companies are recognized as revocation holders, but B's scoped watermark is created as 50 and A's incorrect 42 persists. Observations admitted before the original watermark can consequently be skipped when B evaluates the revocation. A cached snapshot that lacks a revocation cannot establish unique ownership of the legacy key; please make this carry-over conservative and ensure an early mistaken scoped write cannot remain authoritative.

Comment thread crates/kinbase/src/repository.rs Outdated
MrJoy and others added 2 commits October 15, 2026 09:03
A single-form watermark carried over to the one Company whose cached
snapshot carried that revocation, and the carry-over was written to the
Company's scoped key.  A stale snapshot in the Company that really
recorded it made the wrong Company inherit the older cursor, for good,
so its observations admitted in between never reopened.  Nothing on
this machine proves which Company recorded an unqualified watermark,
so none adopts it.  Each starts fresh, which reopens more, never less.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012em5BvVC4KUcSKYMoDr4EK
`repo issue --company B` checked holders and pins but not hints, so it
issued B a certificate for a repository whose origin matched A's hint.
`repo init` counts that hint beside the certificate and refused it as
`company-ambiguous`.  Issuance now refuses the same way, before handing
back a certificate the next step rejects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012em5BvVC4KUcSKYMoDr4EK

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants