Platform Contracts for Builders designed
This page is the missing bridge between platform architecture and product implementation. It is for a team building a second product such as Token Factory, not for an app packager using the App SDK.
If a builder cannot tell what to import, what method to call, and what proof they must emit, the shared-platform docs are still too abstract.
What This Page Covers
| Need | Package anchor | Why it exists |
|---|---|---|
| authorize a product action against platform scope | packages/platform/iam | one platform IAM decision model across products |
| meter product usage into the platform ledger path | packages/platform/billing and packages/shared/events | one accepted usage event path |
| append privileged mutation audit | packages/platform/audit | one audit contract and custody model |
| record release, UAT, security, and rollout proof | packages/platform/evidence | one evidence bundle model |
Minimal Builder Skeleton
The smallest second-product integration should look like this:
- define product id, scopes, usage units, audit actions, and evidence types;
- authorize every privileged or billable path through platform IAM;
- emit metered usage through the shared billing event model;
- append audit for privileged state change;
- record evidence bundle/items for release, UAT, and security proof.
IAM Contract
Use packages/platform/iam for platform-owned permission evaluation. The
builder owns the product scope ids and role intent, but the decision surface is
shared.
Important types and methods:
iam.ScopeDecisionInputiam.Decisioniam.ActionPlatformAdminiam.ActionPlatformOpsReadiam.ActionPlatformOpsWrite(*iam.Service).EvaluateScopePermission(*iam.Service).ResolvePlatformRoles
package tokenfactory
import (
"context"
"github.com/gpuaas/platform/packages/platform/iam"
)
func allowGatewayWrite(
ctx context.Context,
iamSvc *iam.Service,
actorID string,
roles []string,
) error {
decision := iamSvc.EvaluateScopePermission(ctx, iam.ScopeDecisionInput{
ActorType: iam.ActorTypeServiceAccount,
ActorID: actorID,
Roles: roles,
RequiredScope: "token_factory.gateway.write",
RequiredAction: iam.ActionPlatformOpsWrite,
AppliedScope: iam.ScopeProject,
})
if !decision.Allow {
return ErrPermissionDenied
}
return nil
}
Builder rule:
- product code defines which scope id and role posture applies;
- platform IAM decides whether the actor is allowed;
- do not hardcode local “admin means allow” shortcuts inside the product.
Billing And Usage Contract
If the second product is billable, it should not write ledger rows directly.
Emit accepted usage through events.UsageMeteredPayload and let platform
billing consume it.
Important anchors:
billing.Service.RecordUsageMeteredbilling.Service.ValidateUsageUnitevents.UsageMeteredPayloadevents.SubjectBillingUsageMetered
package tokenfactory
import (
"context"
"time"
"github.com/gpuaas/platform/packages/platform/billing"
"github.com/gpuaas/platform/packages/shared/events"
)
func recordTokenUsage(
ctx context.Context,
billingSvc *billing.Service,
now time.Time,
) error {
payload := events.UsageMeteredPayload{
UsageEventID: "usage-evt-123",
UsageSource: "token_factory.gateway",
UsageUnit: "token_output",
ProductID: "token_factory",
OrgID: "org-123",
ProjectID: "project-456",
ResourceType: "model_request",
ResourceID: "req-789",
QuantityMillis: 1,
UnitCount: 2048,
BillFrom: now.Add(-1 * time.Minute).Format(time.RFC3339),
BillTo: now.Format(time.RFC3339),
IdempotencyKey: "token-factory-req-789-token_output",
RequestID: "req-789",
SourceCorrelationID: "corr-123",
Metadata: map[string]any{
"model_id": "llama-70b",
},
}
return billingSvc.RecordUsageMetered(ctx, payload)
}
Builder rule:
- validate or register the usage unit first;
- send one idempotent event per closed usage interval or accepted increment;
- keep pricing, ledger, and invoice behavior platform-owned.
Audit Contract
Every privileged mutation must append a platform audit event. Product code owns the action meaning, but it must use a registered audit action and shared append surface.
Important anchors:
audit.AppendInput(*audit.Service).Append- actor roles:
user,admin,service_account,system
package tokenfactory
import (
"context"
"github.com/gpuaas/platform/packages/platform/audit"
)
func appendGatewayPolicyAudit(
ctx context.Context,
auditSvc *audit.Service,
orgID string,
userID string,
correlationID string,
) error {
_, err := auditSvc.Append(ctx, audit.AppendInput{
OrgID: &orgID,
ActorUserID: &userID,
ActorRole: audit.ActorRoleAdmin,
Action: "token_factory.policy.updated",
TargetType: "token_factory_policy",
Result: audit.ResultSuccess,
CorrelationID: correlationID,
Metadata: map[string]any{
"surface": "gateway-admin",
},
})
return err
}
Builder rule:
- register the action in the audit-action registry before using it;
- keep
target_typestable and specific; - include correlation ids on every privileged mutation path.
Evidence Contract
Use packages/platform/evidence when the product needs release proof, UAT
evidence, security controls evidence, or rollout evidence.
Important anchors:
evidence.CreateBundleInputevidence.RecordItemInputevidence.RecordInvariantInput(*evidence.Service).CreateBundle(*evidence.Service).RecordItem(*evidence.Service).RecordInvariantCoverage
package tokenfactory
import (
"context"
"github.com/gpuaas/platform/packages/platform/evidence"
)
func recordReleaseProof(ctx context.Context, evidenceSvc *evidence.Service) error {
bundle, err := evidenceSvc.CreateBundle(ctx, evidence.CreateBundleInput{
SourceCommit: "abc123",
EnvironmentProfile: "dev",
ProductScope: "token_factory",
ChangeSummary: "gateway billing and IAM integration",
})
if err != nil {
return err
}
_, err = evidenceSvc.RecordItem(ctx, evidence.RecordItemInput{
BundleID: bundle.BundleID,
EvidenceType: "uat.summary",
Producer: "token-factory-uat",
Result: evidence.ResultPass,
Owner: "token-factory",
RetentionClass: "release",
Details: map[string]any{
"flow_id": "TOKEN-FACTORY-LAUNCH-001",
},
})
return err
}
Builder rule:
- product code supplies the product-specific invariant ids and evidence details;
- the bundle, result vocabulary, and custody model remain shared.
Contract Boundaries You Should Not Cross
| Anti-pattern | Why it is wrong | Correct pattern |
|---|---|---|
| product writes ledger rows directly | breaks rating and custody boundary | emit UsageMeteredPayload |
| product invents its own audit table | splits privileged history | use audit.Service.Append |
| product checks local admin flags without shared scope decision | drifts IAM semantics across products | call iam.Service |
| product treats evidence as markdown-only | makes release/UAT proof unreadable to tooling | write evidence bundle/item records |
Before You Start Coding
A second-product builder should be able to answer these four questions before the first handler lands:
- Which product-owned scopes and usage units must be registered first?
- Which privileged mutations require audit append?
- Which user/admin/operator flows produce billable usage?
- Which UAT or release flows must become evidence bundle items?
If those are not explicit, do onboarding and registry work first. Do not push that ambiguity into handler code.