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
5 changes: 5 additions & 0 deletions .changeset/bright-otters-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@emdash-cms/registry-lexicons": minor
---

Adds experimental package profile policy and release provenance lexicon contracts.
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@
"type": "string",
"format": "datetime",
"description": "ISO 8601 datetime for the package's last update."
},
"extensions": {
"type": "unknown",
"description": "Opaque extension container keyed by NSID. The base profile Lexicon does not validate entries; consumers dispatch known entries to their embedded extension schemas. EmDash delegated-release policy uses com.emdashcms.experimental.package.profileExtension here."
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"lexicon": 1,
"id": "com.emdashcms.experimental.package.profileExtension",
"description": "EmDash delegated-release policy embedded inside a package profile record under its extensions field. EXPERIMENTAL: shape may change before stabilisation.",
"defs": {
"main": {
"type": "object",
"description": "Typed embedded object carrying the package repository anchor and optional delegated-release policy. Lives under the profile record's extensions map keyed by this NSID. The $type field is required when embedded; clients use it to dispatch to this schema.",
"required": ["repository"],
"properties": {
"repository": {
"type": "string",
"format": "uri",
"description": "Canonical HTTPS source repository URL. Lexicon validates URI syntax and length; consumers must require HTTPS and canonicalization.",
"maxLength": 1024
},
"releasePolicy": {
"type": "ref",
"ref": "#releasePolicy"
}
}
},
"releasePolicy": {
"type": "object",
"description": "Optional delegated-release policy. Absent fields normalize for consumers to requireProvenance false, confirmation escalation-only, and no approvers.",
"properties": {
"requireProvenance": {
"type": "boolean",
"description": "Whether releases require a verifiable provenance reference."
},
"confirmation": {
"type": "string",
"description": "When a release requires human confirmation. The generated TypeScript type exposes escalation-only and always; Lexicon runtime validators do not enforce knownValues, so consumers must reject other values.",
"knownValues": ["escalation-only", "always"]
},
"approvers": {
"type": "array",
"description": "Atproto DIDs authorized to approve releases. Lexicon validates DID syntax and the 32-item cap; consumers must reject duplicate DIDs.",
"maxLength": 32,
"items": {
"type": "string",
"format": "did"
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,42 @@
"type": "ref",
"ref": "#declaredAccess",
"description": "Structured per-category access manifest. The sandbox enforces every operation declared here at runtime."
},
"provenance": {
"type": "ref",
"ref": "#provenance",
"description": "Optional reference to a provenance document for this release."
}
}
},
"provenance": {
"type": "object",
"description": "Provenance document reference. Unknown predicate types are retained as present-but-unverifiable; consumers understand https://slsa.dev/provenance/v1 in v1. Lexicon validates URI syntax and field lengths; consumers validate multibase-multihash syntax and source and builder canonicalization.",
"required": ["predicateType", "url", "checksum", "sourceRepository", "builderId"],
"properties": {
"predicateType": {
"type": "string",
"maxLength": 1024
},
"url": {
"type": "string",
"format": "uri",
"maxLength": 2048
},
"checksum": {
"type": "string",
"description": "Multibase-encoded multihash of the provenance document bytes. Consumers validate the multibase-multihash syntax and supported hash function.",
"maxLength": 256
},
"sourceRepository": {
"type": "string",
"format": "uri",
"maxLength": 1024
},
"builderId": {
"type": "string",
"format": "uri",
"maxLength": 1024
}
}
},
Expand Down
1 change: 1 addition & 0 deletions packages/registry-lexicons/src/generated/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * as ComEmdashcmsExperimentalAggregatorListReleases from "./types/com/emd
export * as ComEmdashcmsExperimentalAggregatorResolvePackage from "./types/com/emdashcms/experimental/aggregator/resolvePackage.js";
export * as ComEmdashcmsExperimentalAggregatorSearchPackages from "./types/com/emdashcms/experimental/aggregator/searchPackages.js";
export * as ComEmdashcmsExperimentalPackageProfile from "./types/com/emdashcms/experimental/package/profile.js";
export * as ComEmdashcmsExperimentalPackageProfileExtension from "./types/com/emdashcms/experimental/package/profileExtension.js";
export * as ComEmdashcmsExperimentalPackageRelease from "./types/com/emdashcms/experimental/package/release.js";
export * as ComEmdashcmsExperimentalPackageReleaseExtension from "./types/com/emdashcms/experimental/package/releaseExtension.js";
export * as ComEmdashcmsExperimentalPublisherProfile from "./types/com/emdashcms/experimental/publisher/profile.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ const _mainSchema = /*#__PURE__*/ v.record(
/*#__PURE__*/ v.stringGraphemes(0, 140),
]),
),
/**
* Opaque extension container keyed by NSID. The base profile Lexicon does not validate entries; consumers dispatch known entries to their embedded extension schemas. EmDash delegated-release policy uses com.emdashcms.experimental.package.profileExtension here.
*/
extensions: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()),
/**
* Canonical AT URI of this profile record. Derived from the record's location at publish time; not authored by the publisher. Aggregators MUST reject records whose id does not match the AT URI they were fetched from. Mirrors FAIR's id-must-match-the-resolved-identifier rule.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type {} from "@atcute/lexicons";
import * as v from "@atcute/lexicons/validations";

const _mainSchema = /*#__PURE__*/ v.object({
$type: /*#__PURE__*/ v.optional(
/*#__PURE__*/ v.literal(
"com.emdashcms.experimental.package.profileExtension",
),
),
get releasePolicy() {
return /*#__PURE__*/ v.optional(releasePolicySchema);
},
/**
* Canonical HTTPS source repository URL. Lexicon validates URI syntax and length; consumers must require HTTPS and canonicalization.
* @maxLength 1024
*/
repository: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.genericUriString(), [
/*#__PURE__*/ v.stringLength(0, 1024),
]),
});
const _releasePolicySchema = /*#__PURE__*/ v.object({
$type: /*#__PURE__*/ v.optional(
/*#__PURE__*/ v.literal(
"com.emdashcms.experimental.package.profileExtension#releasePolicy",
),
),
/**
* Atproto DIDs authorized to approve releases. Lexicon validates DID syntax and the 32-item cap; consumers must reject duplicate DIDs.
* @maxLength 32
*/
approvers: /*#__PURE__*/ v.optional(
/*#__PURE__*/ v.constrain(
/*#__PURE__*/ v.array(/*#__PURE__*/ v.didString()),
[/*#__PURE__*/ v.arrayLength(0, 32)],
),
),
/**
* When a release requires human confirmation. The generated TypeScript type exposes escalation-only and always; Lexicon runtime validators do not enforce knownValues, so consumers must reject other values.
*/
confirmation: /*#__PURE__*/ v.optional(
/*#__PURE__*/ v.string<"always" | "escalation-only" | (string & {})>(),
),
/**
* Whether releases require a verifiable provenance reference.
*/
requireProvenance: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()),
});

type main$schematype = typeof _mainSchema;
type releasePolicy$schematype = typeof _releasePolicySchema;

export interface mainSchema extends main$schematype {}
export interface releasePolicySchema extends releasePolicy$schematype {}

export const mainSchema = _mainSchema as mainSchema;
export const releasePolicySchema = _releasePolicySchema as releasePolicySchema;

export interface Main extends v.InferInput<typeof mainSchema> {}
export interface ReleasePolicy extends v.InferInput<
typeof releasePolicySchema
> {}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ const _mainSchema = /*#__PURE__*/ v.object({
get declaredAccess() {
return declaredAccessSchema;
},
/**
* Optional reference to a provenance document for this release.
*/
get provenance() {
return /*#__PURE__*/ v.optional(provenanceSchema);
},
});
const _mediaAccessSchema = /*#__PURE__*/ v.object({
$type: /*#__PURE__*/ v.optional(
Expand Down Expand Up @@ -224,6 +230,45 @@ const _pageFragmentsConstraintsSchema = /*#__PURE__*/ v.object({
),
),
});
const _provenanceSchema = /*#__PURE__*/ v.object({
$type: /*#__PURE__*/ v.optional(
/*#__PURE__*/ v.literal(
"com.emdashcms.experimental.package.releaseExtension#provenance",
),
),
/**
* @maxLength 1024
*/
builderId: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.genericUriString(), [
/*#__PURE__*/ v.stringLength(0, 1024),
]),
/**
* Multibase-encoded multihash of the provenance document bytes. Consumers validate the multibase-multihash syntax and supported hash function.
* @maxLength 256
*/
checksum: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
/*#__PURE__*/ v.stringLength(0, 256),
]),
/**
* @maxLength 1024
*/
predicateType: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [
/*#__PURE__*/ v.stringLength(0, 1024),
]),
/**
* @maxLength 1024
*/
sourceRepository: /*#__PURE__*/ v.constrain(
/*#__PURE__*/ v.genericUriString(),
[/*#__PURE__*/ v.stringLength(0, 1024)],
),
/**
* @maxLength 2048
*/
url: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.genericUriString(), [
/*#__PURE__*/ v.stringLength(0, 2048),
]),
});
const _usersAccessSchema = /*#__PURE__*/ v.object({
$type: /*#__PURE__*/ v.optional(
/*#__PURE__*/ v.literal(
Expand Down Expand Up @@ -264,6 +309,7 @@ type networkRequestConstraints$schematype =
type pageAccess$schematype = typeof _pageAccessSchema;
type pageFragmentsConstraints$schematype =
typeof _pageFragmentsConstraintsSchema;
type provenance$schematype = typeof _provenanceSchema;
type usersAccess$schematype = typeof _usersAccessSchema;
type usersReadConstraints$schematype = typeof _usersReadConstraintsSchema;

Expand All @@ -283,6 +329,7 @@ export interface networkAccessSchema extends networkAccess$schematype {}
export interface networkRequestConstraintsSchema extends networkRequestConstraints$schematype {}
export interface pageAccessSchema extends pageAccess$schematype {}
export interface pageFragmentsConstraintsSchema extends pageFragmentsConstraints$schematype {}
export interface provenanceSchema extends provenance$schematype {}
export interface usersAccessSchema extends usersAccess$schematype {}
export interface usersReadConstraintsSchema extends usersReadConstraints$schematype {}

Expand Down Expand Up @@ -312,6 +359,7 @@ export const networkRequestConstraintsSchema =
export const pageAccessSchema = _pageAccessSchema as pageAccessSchema;
export const pageFragmentsConstraintsSchema =
_pageFragmentsConstraintsSchema as pageFragmentsConstraintsSchema;
export const provenanceSchema = _provenanceSchema as provenanceSchema;
export const usersAccessSchema = _usersAccessSchema as usersAccessSchema;
export const usersReadConstraintsSchema =
_usersReadConstraintsSchema as usersReadConstraintsSchema;
Expand Down Expand Up @@ -356,6 +404,7 @@ export interface PageAccess extends v.InferInput<typeof pageAccessSchema> {}
export interface PageFragmentsConstraints extends v.InferInput<
typeof pageFragmentsConstraintsSchema
> {}
export interface Provenance extends v.InferInput<typeof provenanceSchema> {}
export interface UsersAccess extends v.InferInput<typeof usersAccessSchema> {}
export interface UsersReadConstraints extends v.InferInput<
typeof usersReadConstraintsSchema
Expand Down
6 changes: 4 additions & 2 deletions packages/registry-lexicons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export * as AggregatorResolvePackage from "./generated/types/com/emdashcms/exper
export * as AggregatorSearchPackages from "./generated/types/com/emdashcms/experimental/aggregator/searchPackages.js";

export * as PackageProfile from "./generated/types/com/emdashcms/experimental/package/profile.js";
export * as PackageProfileExtension from "./generated/types/com/emdashcms/experimental/package/profileExtension.js";
export * as PackageRelease from "./generated/types/com/emdashcms/experimental/package/release.js";
export * as PackageReleaseExtension from "./generated/types/com/emdashcms/experimental/package/releaseExtension.js";

Expand All @@ -42,6 +43,7 @@ export * as PublisherVerification from "./generated/types/com/emdashcms/experime
*/
export const NSID = {
packageProfile: "com.emdashcms.experimental.package.profile",
packageProfileExtension: "com.emdashcms.experimental.package.profileExtension",
packageRelease: "com.emdashcms.experimental.package.release",
packageReleaseExtension: "com.emdashcms.experimental.package.releaseExtension",
publisherProfile: "com.emdashcms.experimental.publisher.profile",
Expand All @@ -58,7 +60,7 @@ export type NSIDValue = (typeof NSID)[keyof typeof NSID];

/**
* NSIDs of record-shaped lexicons in this package (one row per NSID in the
* publisher's repo). Embedded objects (`releaseExtension`) and shared defs
* publisher's repo). Embedded objects (`profileExtension`, `releaseExtension`) and shared defs
* (`aggregator.defs`) are excluded — they don't address their own collection.
*
* Useful for consumers building OAuth `repo:` scopes or enumerating writable
Expand Down Expand Up @@ -96,7 +98,7 @@ import type * as PublisherVerificationNs from "./generated/types/com/emdashcms/e
* to its PDS. Used by `PublishingClient.putRecord` (and any other typed-write
* helper) to ensure callers pass a record matching the collection's lexicon.
*
* Embedded objects (`releaseExtension`, which lives inside a release record's
* Embedded objects (`profileExtension`, `releaseExtension`, which live inside profile and release records'
* `extensions` map) and query/procedure NSIDs (the `aggregator.*` ones) are
* deliberately absent -- they aren't standalone repo collections.
*/
Expand Down
Loading
Loading