Browse documentation
Product documentation · snapshot 2026-11-03 · View source ↗

These are the product’s technical docs at this revision. Historical version names and status notes are preserved; see Releases for download qualifications.

Read as Markdown

Portable typed artifacts — hn-artifact/1

On this page

Artifacts are generic signed data, available through hn artifact without a registered executable. Document, ordered list and ticket shapes can hold custom models while native hn/0 and hn-memory/0 histories remain unchanged. These protocols are additive; pre-extension readers are not promised custom support. No type name, signature, schema success, relationship or search match activates doctrine, chooses policy, grants prompt/business authority or runs code.

Public identity and wire

The normative byte grammar is the frozen artifact contract. It is a bounded Hubnot JSON encoding, not unrestricted JSON Schema or JCS.

For offline use, extract the source archive matching the package's BUILD.json / PROVENANCE.json source revision, or use a local checkout at that revision, then read kitty-specs/optional-extensions-01M661C0/contracts/artifact-v1.md as Markdown or plain text. Binary packages omit that contract directory; their matching source archive includes all four canonical contracts. No Go toolchain or GitHub runtime connection is required. The contract reference links the same version-pinned host, artifact, fixture and default-workflow files.

Canonical JSON C has UTF-8-byte-sorted object keys, preserved array order, exact Unicode scalar text without normalization, no BOM/whitespace/final newline. Quotes/backslashes/control characters escape; less-than/greater-than/ampersand and U+2028/U+2029 use the contract's lowercase Unicode escapes. Numbers are decimal safe integers -9007199254740991..9007199254740991, with no fraction/exponent/plus, leading zero or negative zero. Duplicate/unknown fields, malformed UTF-8/unpaired surrogates and noncanonical signed payload bytes refuse.

D(bytes) is sha256: plus lowercase SHA-256 hex. Actor is SHA-256 of the raw 32-byte Ed25519 public key. Public key/signature are canonical unpadded standard Base64. Signature preimage is UTF8(hn-artifact/1) || NUL || C(envelope); artifact ID is D(C(envelope)), not a Git OID or digest of signature serialization. Stream is D(UTF8(hn-artifact-stream-v1) || NUL || UTF8(actor)).

Every envelope contains exactly protocol, kind, actor, public_key, stream, sequence, previous, timestamp, operation, request, record, parents, payload. Kinds are type.publish, record.open, record.revise, record.resolve, record.convert. Sequence starts at one; previous is empty only at genesis, otherwise the exact prior signed ID in that actor stream. Timestamp is descriptive UTC RFC3339 seconds, not causal order. Operation is 1..128 printable non-whitespace ASCII bytes. Parents are increasing distinct full IDs, at most 1024.

Request identity is (protocol, actual actor, operation) across all custom kinds/types, separate from native hn/0. Request digest is D(UTF8(hn-artifact-request-v1) || NUL || C({actor,kind,operation,parents,payload,protocol,record})). It excludes timestamp/stream position but binds exact content/order/schema/parents. A frame correlation ID never substitutes for it.

Each actor has an append-only Git stream at refs/hn/artifacts/ACTOR/STREAMHEX; accepted remote observations are refs/hn/remotes/REMOTE/artifacts/ACTOR/STREAMHEX. Each commit has only two regular 100644 blobs: canonical artifact.json and unpadded signature, no newline. The first commit has no parent; later commits have the prior stream commit as sole parent. Actor histories extend by prefix; never merge divergent actor chains. Readers verify Git structure, signature, identity, sequence/previous and closure. SHA-1 and SHA-256 Git repositories retain the same artifact IDs.

Types and pure schemas

Type payload is exactly {type_id,schema_version,shape,schema}. Type ID is namespace/name, each segment [a-z][a-z0-9.-]{0,63}; hn/hubnot namespaces are reserved. Schema version is 1..2147483647. Shape is document, list or ticket. Schema has language=hn.schema/1 and an object root; it validates only fields.

Node type Allowed other keywords
object Required properties, required, additionalProperties=false
array Required items and maxItems (0..1024)
string Required maxBytes (0..1048576), optional enum
integer Optional inclusive minimum/maximum and enum, safe integers only
boolean Optional enum
null None

Required lists and enums are distinct; enums have 1..1024 same-type values. Field names match [A-Za-z_][A-Za-z0-9_.-]{0,63}. At most 64 properties/object; unknown fields refuse. There are no defaults/coercions, remote/schema references, regex validators, unions, recursion, code or executable validators. Unknown schema semantics is unsupported_schema, never accepted validity. Modeled object/array depth is at most eight, counting the modeled root as one, not schema keyword maps.

Initial publication has no parents. Evolution names one verified prior declaration of the same type/shape with strictly smaller schema_version. Different actors can publish the same name/version; names confer no namespace ownership/winner. Records pin an exact declaration ID. A new declaration never reinterprets old records.

Every record payload has shape, type_id, type_revision, title, content, fields, members, relationships, conversion_sources. Title is nonblank UTF-8 1..1024 bytes; content preserves exact UTF-8 text, possibly empty; no binary attachment. Fields is the full object validated by its exact declaration. Envelope size bounds the aggregate content. Document/ticket have members=[]; ticket business statuses are ordinary schema fields, not built-in lifecycle authority.

A list member is {member_id,target,target_mode,required}. IDs are unique within the revision and match [A-Za-z0-9][A-Za-z0-9._-]{0,127}. Array position is order, not member ID or timestamp. Retain a member ID when retaining its identity. A relationship is {relation,target,target_mode,required}; relation uses type-ID grammar. Relationship order is preserved but is not execution order. No duplicate relationship tuple; at most 256 relationships and 1024 members.

Targets are custom-record full IDs. logical names an opening and selects all current heads; revision pins an exact signed opening/successor. Required links join public closure; missing required schema/target is incomplete_dependency. Missing optional targets produce missing_optional diagnostics. A conflicted logical target produces target_conflict, never a chosen winner. Domain cycles terminate through bounded visited-set traversal; ancestry/stream cycles are invalid. Other-protocol links are descriptive fields, not silently traversed dependencies.

Operation Parent/schema behavior
open record=null at CLI, parents=[], conversion_sources=[]; creates logical opening ID
revise One current sole head, unchanged exact type revision, conversion_sources=[]
convert One current sole head, same type/shape/opening, new exact declaration, conversion_sources=[parent]; full new data, no converter runs
resolve Every current head in increasing order; explicit chosen schema/full data; conversion_sources exactly the parents with a different schema

Conflict has multiple heads and state=null. Stale/missing/extra parents refuse stale_revision; revise on conflict refuses resource_conflict. Do not silently choose timestamps, update parents, partially resolve or retry changed intent. Imported historically valid branches remain facts even when their parents are not today's heads. A third branch after an earlier two-head resolution can still leave a conflict. Same-actor stream divergence is actor_stream_conflict.

CLI and exact recovery

hn artifact type publish --input FILE|- --actor ACTOR --operation KEY --json
hn artifact type show FULL_ID --json
hn artifact open|revise|resolve|convert --input FILE|- --actor ACTOR --operation KEY --json
hn artifact show|heads|history FULL_ID --json
hn artifact list [--type TYPE_ID] [--shape document|list|ticket] --limit 1..200 [--cursor TOKEN] --json
hn artifact search [--type TYPE_ID] --literal TEXT|--regex PATTERN --limit 1..200 [--cursor TOKEN] --json
hn artifact operation --type TYPE_ID --actor ACTOR --operation KEY --json
hn artifact inspect --id FULL_ID --json
hn artifact export --id FULL_ID [--id FULL_ID] --output FILE --json
hn artifact import --input FILE|- [--preserve-opaque] --json
hn artifact push|fetch --remote NAME --id FULL_ID [--id FULL_ID] --json

Vertical alternatives describe command grammar, not literal shell commands. Type publication input is {parents,payload}; record mutation input is {record,parents,payload}. --actor/--operation are separate from input. Use real returned full IDs; no prefix lookup. JSON uses {ok,data,error}, exits 0/1, and the closed error/recovery vocabulary. The copyable inventory journey generates the IDs used for its later commands.

These exit/JSON rules describe direct hn artifact commands. When a publisher uses hn extension run, that parent command reports session management; a completed session can still write a business ok:false to its private result file. Inspect that file separately, as explained in the extension lifecycle.

Persist original actor/key, target, exact payload and parents before sending. Exact replay looks up the key before comparing current heads, returns original identities and appends nothing; changed replay is operation_conflict. For a new intent, validate actor/history/schema/closure/policy and exact heads, persist a private immutable intent, sign, publish by expected-old CAS, durably record result, then acknowledge. Custom writes under selected contribution policy refuse unsupported_capability before intents/signing/publication.

After lost acknowledgement, run operation with original type/actor/key. Found proves original request/result; verified_absent requires complete verified history and no pending publication evidence; uncertain requires inspection/settlement and never authorizes a new key or changed request. Pending private intent can keep a publicly absent operation uncertain. Missing intent does not negate a found public operation. CAS concurrency uses retry_identical, not an automatic resign/retry. Private intent/query state is not public proof and operation lookup does not repair or create an intent. Global exactly-once or remote API reconciliation is not promised.

Plugin-free exchange and generic reads

Configure a real Git remote explicitly in both repositories. Push/fetch select IDs and required public closure; ordinary git clone alone does not promise custom refs. Closure includes actor predecessors, opening/parents, exact schema ancestry, conversion sources and required typed links/members. Exact historical selections never silently become latest; select a later revision separately when desired.

Selected file exchange is closed UTF-8 JSON: {protocol:"hn-artifact-exchange/1",mode:"accepted"|"opaque",refs:[{ref,oid}],objects:[{oid,type,data}]}. Object type is blob/tree/commit; data is canonical unpadded Base64 raw Git body. Verify each Git hash and complete selected closure before expected-old atomic accepted-ref promotion. Failure promotes none of that transaction. No tag/private file/signing key/registration/grant/journal enters public export. Git network download is not claimed to have a hard pre-download byte quota.

inspect --id reports signed selected closure; locate the requested exact item. show, heads, list and search report current logical heads; history preserves old schema/content/order. Search matches current title/content, canonical fields and public identity metadata. CLI search has no --shape; CLI list does. Host artifact.search separately accepts a shape parameter. Cursors bind query/filter and source cut; invalidation means restart without cursor. No silent truncation.

Opaque preservation requires explicit import --preserve-opaque; it remains unverified diagnostic public quarantine, never ordinary accepted data. Opaque ID is D(UTF8(Git object type) || NUL || raw Git body), not a signed artifact ID.

hn artifact inspect --opaque --opaque-id FULL_OPAQUE_ID --json
hn artifact export --opaque --opaque-id FULL_OPAQUE_ID --output FILE --json

Only explicitly preserved public quarantine objects are addressable; no arbitrary ODB, working-tree, signing key or private journal access. Opaque export is not accepted import/promotion. Diagnostics distinguish incomplete_dependency, unsupported_schema, target_conflict, missing_optional, invalid_history and opaque_unverified; they do not grant semantic authority.

Removing the optional module/binary leaves generic type/read/history/search/export and native collaboration available. Transfer public history, never source private identity/registration/grants. Native commands ignore unrelated malformed/unknown custom histories; generic artifact reads may diagnose them.

Bounds and inert utilities

Equality is accepted when otherwise valid; first-over refuses, not truncates.

Boundary Maximum
Canonical envelope / private intent file 1 MiB / 2 MiB
Type payload / included schema 128 KiB / 64 KiB
Modeled schema/value container depth 8
Properties/object; arrays/list/parents/enums 64; 1024
Relationships 256
Exchange wire / decoded aggregate 16 MiB / 32 MiB
Exchange objects / signed records / ancestry depth 4096 / 2048 / 256
Read selected refs/records 100000
Read Git object / aggregate 8 MiB / 256 MiB
Regex UTF-8 bytes / expanded-compiled instructions 4096 / 4096
Regex complete-selection instruction-bytes 67108864

Regex uses bounded Go syntax and validates the whole filtered work budget before output. Narrow type selection/simplify regex on resource_limit. One oversized complete state cannot be fixed by reducing page size; use explicit export. Read/copy/validation stages have finite 30-second contexts; publication uncertainty still applies if dispatched I/O cannot promptly settle.

hn artifact schema|record|type validate --input FILE|- --json checks bounded supplied values, not publication or business authority. hn artifact envelope validate|inspect --input FILE|- --signature RAW_BASE64 --json verifies canonical public bytes/signature. Optional paired --previous-input FILE and --previous-signature RAW_BASE64 prove only that supplied adjacent predecessor; without it a non-genesis envelope reports continuity not_checked. Inspect adds the decoded envelope. Neither utility proves complete history/schema/closure or accepted visibility. See the normative limits and utility contracts.