Package spec

The contract reviewers approve.

A Lantern app package is a browser-first activity plus a manifest. The manifest says what the app is, what files belong to it, which roles can use it, and which runtime capabilities it asks Lantern to provide.

Scope

v1 is intentionally narrow.

The package spec supports learner-facing course activities, games, quizzes, simulations, tutors, and practice tools. It does not support arbitrary backend code, direct LMS API access, direct D1 database access, arbitrary outbound HTTP, or direct grade writes.

The goal

  • Make app generation easy enough for real course use.
  • Make review concrete enough for institutional governance.
  • Make unsafe shortcuts invalid by default.

Layout

The package is small enough to inspect.

app/
  manifest.json
  dist/
    index.html
    assets/*
  content/
    activity.json
  scoring/
    rubric.json
  preview/
    fixtures.json
    tests.json
  grading/
    specs/
      *.js
  evidence/
    example-output.json

manifest.json and dist/index.html are required. The rest depends on the app. Once a package version is signed, treat it as immutable.

Manifest

The manifest is the review contract.

Required fields

  • schema_version
  • app_id
  • version
  • title
  • owner
  • entrypoint
  • capabilities
  • roles
  • grading

Optional fields

  • description
  • icon
  • install_scope
  • browser
  • content_files
  • preview
  • authoring
{
  "schema_version": "1",
  "app_id": "chapter-4-asteroids",
  "version": "0.1.0",
  "title": "Chapter 4 Asteroids",
  "owner": { "type": "user", "id": "instructor_123" },
  "entrypoint": "/dist/index.html",
  "roles": ["learner", "instructor"],
  "install_scope": "course",
  "capabilities": [
    "read_launch_context",
    "read_activity_content",
    "submit_attempt_event",
    "submit_evidence_artifact",
    "finalize_attempt",
    "read_local_state",
    "write_local_state"
  ],
  "grading": { "mode": "browser", "max_score": 100 },
  "content_files": ["/content/activity.json"],
  "preview": {
    "fixtures_file": "/preview/fixtures.json",
    "tests_file": "/preview/tests.json"
  },
  "authoring": {
    "kind": "browser_autograder",
    "grader_spec_files": ["/grading/specs/checks.spec.js"],
    "evidence_example_file": "/evidence/example-output.json"
  }
}

Fields

What the important fields mean.

schema_versionString. v1 starts at "1".
app_idThe stable logical id for the app across versions.
versionThe immutable artifact version. Semver works well.
ownerThe responsible owner, shaped as { "type": "user", "id": "user_123" } in v1.
entrypointThe HTML entry file inside the package, usually /dist/index.html.
rolesAllowed values are learner and instructor.
install_scopeAllowed values are course and assignment. Default is course.
browserA review request, not a permission grant. v1 supports fullscreen and clipboard_write, both false by default.

Allowed v1 capabilities

  • read_launch_context
  • read_activity_content
  • submit_attempt_event
  • submit_evidence_artifact
  • finalize_attempt
  • read_local_state
  • write_local_state

submit_evidence_artifact requires finalize_attempt. It keeps structured JSON evidence and optional screenshot evidence on the same Lantern-owned path.

Grading modes

  • declarative: Lantern computes a score from reviewed rules.
  • manual: hold for instructor review.
  • completion: credit follows completion.
  • browser: Lantern runs reviewed browser grader specs.

Authoring fields

authoring describes reviewable artifacts. It does not grant runtime permissions.

  • kind = "browser_autograder"
  • grader_spec_files under grading/specs/*.js
  • evidence_example_file, usually /evidence/example-output.json

SDK

The SDK is small by design.

type LaunchContext = {
  userRole: 'learner' | 'instructor';
  courseId: string;
  assignmentId?: string;
  activityId: string;
  submissionMode: 'standard' | 'anonymous_submission';
};

declare function getLaunchContext(): Promise<LaunchContext>;
declare function getActivityContent<T = unknown>(): Promise<T>;
declare function readLocalState<T = unknown>(): Promise<T | null>;
type AttemptEvent =
  | {
      type: 'answer';
      questionId: string;
      answer: string | string[];
      correct?: boolean;
      scoreGiven?: number;
      scoreMaximum?: number;
      timestamp: string;
    }
  | { type: 'progress'; checkpoint: string; value: number; timestamp: string }
  | { type: 'complete'; timestamp: string };

declare function emitAttemptEvent(event: AttemptEvent): Promise<void>;
declare function finalizeAttempt(input: {
  completionState: 'completed' | 'abandoned';
}): Promise<{ accepted: true }>;
declare function writeLocalState<T = unknown>(value: T): Promise<void>;

Forbidden SDK surface

  • getCanvasAccessToken()
  • writeGrade()
  • runSql()
  • fetch(url)
  • assumeRole()
  • readFullRoster()

If a package needs one of these, it is outside the v1 trust boundary.

Review

Publication depends on evidence, not hope.

Preview contract

  • Fake launch context.
  • Fake course and assignment ids.
  • Fake learner and instructor roles.
  • Fake attempt ids.
  • Visible capability log.
  • Fake scoring response.

Review contract

  • Manifest validation passed.
  • Bundle is present.
  • Preview is runnable.
  • Accessibility and security checks passed or are flagged.
  • Artifact is signed.
  • Reviewer is recorded.

Courses install an app version, not just an app id. That gives the institution reproducibility, review history, rollback, and auditable deployment.

If someone needs raw LMS tokens, a small backend, or one arbitrary URL, the v1 answer is no. That discipline is the point of the package spec.