-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(capabilities)!: add index/add
capability
#1410
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/** | ||
* Index Capabilities. | ||
* | ||
* W3 Indexing protocol allows authorized agents to submit verifiable claims | ||
* about content-addressed data to be published on the InterPlanetary Network | ||
* Indexer (IPNI), making it publicly queryable. | ||
* | ||
* These can be imported directly with: | ||
* ```js | ||
* import * as Index from '@web3-storage/capabilities/index' | ||
* ``` | ||
* | ||
* @module | ||
*/ | ||
import { CAR } from '@ucanto/core' | ||
import { capability, Schema, ok } from '@ucanto/validator' | ||
import { equalWith, SpaceDID, and, equal } from '../utils.js' | ||
|
||
/** | ||
* Capability can only be delegated (but not invoked) allowing audience to | ||
* derive any `index/` prefixed capability for the space identified by the DID | ||
* in the `with` field. | ||
*/ | ||
export const index = capability({ | ||
can: 'index/*', | ||
/** DID of the space where indexed data is stored. */ | ||
with: SpaceDID, | ||
derives: equalWith, | ||
}) | ||
|
||
/** | ||
* `index/add` capability allows an agent to submit verifiable claims | ||
* about content-addressed data to be published on the InterPlanetary Network | ||
* Indexer (IPNI), making it publicly queryable. | ||
*/ | ||
export const add = capability({ | ||
can: 'index/add', | ||
/** DID of the space where indexed data is stored. */ | ||
with: SpaceDID, | ||
nb: Schema.struct({ | ||
/** Content Archive (CAR) containing the `Index`. */ | ||
index: Schema.link({ code: CAR.code }), | ||
}), | ||
derives: (claimed, delegated) => | ||
and(equalWith(claimed, delegated)) || | ||
and(equal(claimed.nb.index, delegated.nb.index, 'index')) || | ||
ok({}), | ||
}) | ||
|
||
// ⚠️ We export imports here so they are not omitted in generated typedefs | ||
// @see https://github.com/microsoft/TypeScript/issues/51548 | ||
export { Schema } |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ import * as StorefrontCaps from './filecoin/storefront.js' | |
import * as AggregatorCaps from './filecoin/aggregator.js' | ||
import * as DealTrackerCaps from './filecoin/deal-tracker.js' | ||
import * as DealerCaps from './filecoin/dealer.js' | ||
import * as IndexCaps from './index/index.js' | ||
import * as AdminCaps from './admin.js' | ||
import * as UCANCaps from './ucan.js' | ||
import * as PlanCaps from './plan.js' | ||
|
@@ -53,6 +54,8 @@ export interface UCANAwait<Selector extends string = string, Task = unknown> { | |
*/ | ||
export type CARLink = Link<unknown, typeof CAR.codec.code> | ||
|
||
export type Multihash = Uint8Array | ||
|
||
export type AccountDID = DID<'mailto'> | ||
export type SpaceDID = DID<'key'> | ||
|
||
|
@@ -449,6 +452,37 @@ export type UploadGetFailure = UploadNotFound | Ucanto.Failure | |
// HTTP | ||
export type HTTPPut = InferInvokedCapability<typeof HTTPCaps.put> | ||
|
||
// Index | ||
export type Index = InferInvokedCapability<typeof IndexCaps.index> | ||
export type IndexAdd = InferInvokedCapability<typeof IndexCaps.add> | ||
|
||
export type IndexAddSuccess = Unit | ||
|
||
export type IndexAddFailure = | ||
| UnknownFormat | ||
| ShardNotFound | ||
| SliceNotFound | ||
| Failure | ||
|
||
/** The index is not in a format understood by the service. */ | ||
export interface UnknownFormat extends Failure { | ||
name: 'UnknownFormat' | ||
} | ||
|
||
/** A shard referenced by the index is not stored in the referenced space. */ | ||
export interface ShardNotFound extends Failure { | ||
name: 'ShardNotFound' | ||
/** Multihash digest of the shard that could not be found. */ | ||
digest: Multihash | ||
} | ||
|
||
/** A slice referenced by the index was not found in the specified shard. */ | ||
export interface SliceNotFound extends Failure { | ||
name: 'SliceNotFound' | ||
/** Multihash digest of the slice that could not be found. */ | ||
digest: Multihash | ||
} | ||
|
||
// Blob | ||
export type Blob = InferInvokedCapability<typeof BlobCaps.blob> | ||
export type BlobAdd = InferInvokedCapability<typeof BlobCaps.add> | ||
|
@@ -458,9 +492,8 @@ export type ServiceBlob = InferInvokedCapability<typeof W3sBlobCaps.blob> | |
export type BlobAllocate = InferInvokedCapability<typeof W3sBlobCaps.allocate> | ||
export type BlobAccept = InferInvokedCapability<typeof W3sBlobCaps.accept> | ||
|
||
export type BlobMultihash = Uint8Array | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a breaking change, I think this is imported somewhere else IIRC. Either should be kept, or need to be flagged There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps is just in |
||
export interface BlobModel { | ||
digest: BlobMultihash | ||
digest: Multihash | ||
size: number | ||
} | ||
|
||
|
@@ -841,7 +874,9 @@ export type ServiceAbilityArray = [ | |
ServiceBlob['can'], | ||
BlobAllocate['can'], | ||
BlobAccept['can'], | ||
HTTPPut['can'] | ||
HTTPPut['can'], | ||
Index['can'], | ||
IndexAdd['can'] | ||
] | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import assert from 'assert' | ||
import { access } from '@ucanto/validator' | ||
import { Verifier } from '@ucanto/principal' | ||
import * as Index from '../../src/index/index.js' | ||
import * as Capability from '../../src/top.js' | ||
import { | ||
alice, | ||
service as w3, | ||
mallory as account, | ||
bob, | ||
} from '../helpers/fixtures.js' | ||
import { createCarCid, validateAuthorization } from '../helpers/utils.js' | ||
|
||
const top = async () => | ||
Capability.top.delegate({ | ||
issuer: account, | ||
audience: alice, | ||
with: account.did(), | ||
}) | ||
|
||
const index = async () => | ||
Index.index.delegate({ | ||
issuer: account, | ||
audience: alice, | ||
with: account.did(), | ||
proofs: [await top()], | ||
}) | ||
|
||
describe('index capabilities', function () { | ||
it('index/add can be derived from *', async () => { | ||
const add = Index.add.invoke({ | ||
issuer: alice, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { | ||
index: await createCarCid('test'), | ||
}, | ||
proofs: [await top()], | ||
}) | ||
|
||
const result = await access(await add.delegate(), { | ||
capability: Index.add, | ||
principal: Verifier, | ||
authority: w3, | ||
validateAuthorization, | ||
}) | ||
|
||
if (result.error) { | ||
assert.fail(result.error.message) | ||
} | ||
|
||
assert.deepEqual(result.ok.audience.did(), w3.did()) | ||
assert.equal(result.ok.capability.can, 'index/add') | ||
assert.deepEqual(result.ok.capability.nb, { | ||
index: await createCarCid('test'), | ||
}) | ||
}) | ||
|
||
it('index/add can be derived from index/*', async () => { | ||
const add = Index.add.invoke({ | ||
issuer: alice, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { | ||
index: await createCarCid('test'), | ||
}, | ||
proofs: [await index()], | ||
}) | ||
|
||
const result = await access(await add.delegate(), { | ||
capability: Index.add, | ||
principal: Verifier, | ||
authority: w3, | ||
validateAuthorization, | ||
}) | ||
|
||
if (result.error) { | ||
assert.fail(result.error.message) | ||
} | ||
|
||
assert.deepEqual(result.ok.audience.did(), w3.did()) | ||
assert.equal(result.ok.capability.can, 'index/add') | ||
assert.deepEqual(result.ok.capability.nb, { | ||
index: await createCarCid('test'), | ||
}) | ||
}) | ||
|
||
it('index/add can be derived from index/* derived from *', async () => { | ||
const index = await Index.index.delegate({ | ||
issuer: alice, | ||
audience: bob, | ||
with: account.did(), | ||
proofs: [await top()], | ||
}) | ||
|
||
const add = Index.add.invoke({ | ||
issuer: bob, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { | ||
index: await createCarCid('test'), | ||
}, | ||
proofs: [index], | ||
}) | ||
|
||
const result = await access(await add.delegate(), { | ||
capability: Index.add, | ||
principal: Verifier, | ||
authority: w3, | ||
validateAuthorization, | ||
}) | ||
|
||
if (result.error) { | ||
assert.fail(result.error.message) | ||
} | ||
|
||
assert.deepEqual(result.ok.audience.did(), w3.did()) | ||
assert.equal(result.ok.capability.can, 'index/add') | ||
assert.deepEqual(result.ok.capability.nb, { | ||
index: await createCarCid('test'), | ||
}) | ||
}) | ||
|
||
it('index/add should fail when escalating index constraint', async () => { | ||
const delegation = await Index.add.delegate({ | ||
issuer: alice, | ||
audience: bob, | ||
with: account.did(), | ||
nb: { | ||
index: await createCarCid('test'), | ||
}, | ||
proofs: [await top()], | ||
}) | ||
|
||
const add = Index.add.invoke({ | ||
issuer: bob, | ||
audience: w3, | ||
with: account.did(), | ||
nb: { | ||
index: await createCarCid('test2'), | ||
}, | ||
proofs: [delegation], | ||
}) | ||
|
||
const result = await access(await add.delegate(), { | ||
capability: Index.add, | ||
principal: Verifier, | ||
authority: w3, | ||
validateAuthorization, | ||
}) | ||
|
||
assert.ok(result.error) | ||
assert(result.error.message.includes('violates imposed index constraint')) | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a question, are we going to validate this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have talked previously about sampling a few in storacha/specs#85