ThParty.mesa
Copyright Ó 1985, 1986, 1988 by Xerox Corporation. All rights reserved.
Last modified by Swinehart, February 20, 1988 6:50:23 pm PST
Parties and Conversations
DIRECTORY
BasicTime USING [ GMT ],
GVBasics USING [ Password ],
IV,
LoganBerry USING [ Entry ],
RefID USING [ ID ],
RPC USING [ InterfaceName, ShortROPE ],
Thrush USING [
ActionReport, ConversationID, ConvEvent, Credentials, EncryptionKey, InterfaceSpec, KeyTable, NetAddress, NB, nullID, noAddress, PartyID, PartyType, Reason, ROPE, SHHH, SmartsID, StateInConv, unencrypted, VoiceSocket ],
ThSmartsRpcControl
;
ThParty: CEDAR DEFINITIONS = {
Definitions
ConversationID: TYPE = Thrush.ConversationID;
PartyID: TYPE = Thrush.PartyID;
SmartsID: TYPE = Thrush.SmartsID;
nullID: RefID.ID = Thrush.nullID;
Credentials: TYPE = Thrush.Credentials;
NB: TYPE = Thrush.NB;
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Thrush.SHHH;
none: SHHH = Thrush.unencrypted;
SmartsInterfaceName: TYPE = RPC.InterfaceName;
SmartsInterfaceRecord: TYPE = ThSmartsRpcControl.InterfaceRecord;
SmartsProperties: TYPE=RECORD[
role: SmartsRole←NIL,
netAddress: Thrush.NetAddress←Thrush.noAddress
The netAddress is the address to use for establishing voice connections, including a socket number that does not change for the life of the Smarts. A smarts must not be active in more than one conversation at a time, so this unique assignment works.
];
ConvType: TYPE = { none, interactive, meeting };
$none is used by some software to indicate that no type has yet been accommodated. See LarkOutImpl.
$interactive is ordinary "full-duplex" conversation among 2 or more parties;
$meeting is conversation involving one moderator, possibly one commentator, and n-c-1 auditors.
SmartsRole: TYPE= ATOM; -- { $voiceTerminal, $controller, ? };
voiceTerminal represents a role corresponding to Lark Smarts, front or back door, or BluejaySmarts; an actual source/sink of audio information, as well as the smarts control functions.
controller represents an entity that can perform control functions, but cannot supply or accept the voice transmission protocol. Standard and only present example: FinchSmarts. Controllers generally get to hear about changes in conversations before their associated voice terminals do.
AccessList: TYPE = LIST OF ROPE; -- access control list for conversations
Conversation, Party Attributes
Attributes: TYPE = LoganBerry.Entry; -- Property list
Conversation attributes:
Subject, name, urgency, ... -- name is for named, meeting-style conversations.
CallUrgency: TYPE= ATOM; -- {
$junk, $ifConvenient, $normal=NIL, $important, $urgent, $fire};
Party attributes:
Priority, alertKind, ...
AlertKind: TYPE = ATOM; -- { $queryOnly, $standard=NIL, $intercom, $background, ... };
As parameters to ThParty.Alert and ThSmarts.Alert:
queryOnly: is it likely that Alert would succeed? conversation argument can be null.
standard: ring the phone, or whatever recipient would like.
intercom: try to get through without ringing.
Any party can contribute/replace conversation attributes to the conversation, or party attributes to its own entry. Removing attributes is not yet supported, although a NIL value effectively cancels an attribute.
Conversation Management
Discussion
A Smarts must relay all call-placement requests through a Party it is associated with. This is because we don't trust all Smarts as much as we trust all Parties; the Parties should validate the requests. The party level, however, only concerns itself with maintaining the "truth" about the current state of all parties in a conversation, reporting state transitions to interested smarts, and storing/computing some values that smarts may be interested in. Smarts are responsible for initiating their own state transitions and in interpreting the meaning of being in one state or another.
Exceptions: 1) One smarts may advance another party into the $notified state in order to initiate a call. 2) When all parties in a conversation are in the $idle state, the party code can eliminate all the data structures associated with the conversation.
CreateConversation produces a new conversation and puts the calling party in it, in the indicated state; one can destroy the conversation only by idling onesself with respect to it, and expecting others to do the same. CreateConversation accepts other call parameters as a convenience. The conversation's creator is henceforth known to the system as the $originator of the conversation. checkConflict causes a test for front-door/back-door conflict to be made as the conversation is initiated. See PartyConflict.
Alert adds another party to the conversation, in $notified state -- this is always legal, assuming the party's not in the conversation already! The notified party is expected to advance to a further state and report it to the $originator (caller of Alert). The originator can not subsequently influence the behavior of the notified party, except by suggestion.
Advance is called by smarts to progress its own party through a call. See Thrush.StateInConv for a description of the states and their meanings. The reason/comment fields are most useful when going idle, to indicate whether it was due to some problem or just because the call is over. If bilateral is requested, an Advance to $active will fail if >1 parties are already active in the conversation, else will enter a state where additional attempts to become active will fail until the requesting party leaves active participation.
Most ThParty functions return an NB (nota bene) code. If this value is not $success, usually any other return values will be NIL, nullID, or meaningless, and the requested operation will not have been performed. The NB values indicate generally what went wrong. $stateMismatch means that some other party has changed state and the caller has not yet been informed of that. This will only be returned from functions that operate on the conversation state.
CreateConversation: PROC[
shhh: SHHH ← none,
credentials: Credentials,
state: Thrush.StateInConv←$initiating,
reason: Thrush.Reason ← NIL, -- if present, state is probably $failed (to get error signals when no conversation was ever established.)
comment: ROPENIL,
convAttributes: Attributes←NIL, -- added to conversation list
partyAttributes: Attributes←NIL, -- added to own party list
checkConflict: BOOLFALSE
] RETURNS [ nb: NB, convEvent: Thrush.ConvEvent ];
Alert: PROC[
shhh: SHHH ← none,
credentials: Credentials,
calledPartyID: PartyID←nullID,
comment: ROPENIL,
convAttributes: Attributes←NIL,
partyAttributes: Attributes←NIL -- initializes alerted party's list!
] RETURNS [nb: NB ];
ConvEvent will be reported to alerted party, not us; if this fails, give up on the call.
Advance: PROC[
shhh: SHHH ← none,
credentials: Credentials,
state: Thrush.StateInConv,
reportToAll: BOOLFALSE,
IF TRUE, advance is reported to all parties; if FALSE, to calling party only (smarts other than the calling smarts; in no case is an advance reported back to the caller).
reason: Thrush.Reason←NIL, -- wontSay
comment: ROPENIL,
convAttributes: Attributes←NIL,
partyAttributes: Attributes←NIL, -- own
bilateral: BOOLFALSE, -- no more than two active parties in this conversation, please, while this party is an active participant.
checkConflict: BOOLFALSE -- The state we are advancing to will require that the associated voice hardware be in use; disallow transitions that would make this true of two different conversations for the same party (apply to poachees, inherit by poachers?)
] RETURNS [nb: NB, convEvent: Thrush.ConvEvent ];
See discussion with the definition of Thrush.ActionReport. See also Register/ LookupServiceInterface, below, and ThSmarts.ReportAction. ActionReport's other field should be the credentials of the caller; self will be filled in during the reporting process.
ReportAction: PROC[
shhh: SHHH ← none,
report: Thrush.ActionReport, -- includes credentials (in other field) identifying the service party.
reportToAll: BOOLFALSE,
IF TRUE, a copy of the report is delivered to all parties (except the caller); if FALSE, to originating party only (and its poachee, if any).
selfOnCompletion: BOOLFALSE
IF TRUE, a copy of the report is delivered to the reporting party, after all other progress reports have been delivered (no such copy is delivered if numReportsIssued is 0). See the discusson in RegisterKey for use of this feature. This could lead back to Finch-caused delays and such, but at least does not prevent anybody from hanging up.
] RETURNS [nb: NB, numReportsIssued: NAT𡤀];
Enumerations, registrations, queries
ConversationInfo: TYPE = REF ConversationInfoRec;
ConversationInfoRec: TYPE = RECORD [
Information about the conversation as a whole
convID: Thrush.ConversationID←NULL,
numParties: NATTRASH,
numActive: NATTRASH,
numIdle: NATTRASH,
originator: Thrush.PartyID ← TRASH,
conferenceHost: Thrush.NetAddress←TRASH,
convType: ConvType←$none, -- meeting or interactive
bilateralConv: BOOLFALSE, -- conversation restricted to two parties
startTime: BasicTime.GMT←TRASH,
convAttributes: Attributes←NIL, -- conversation-wide attributes
moderator: Thrush.PartyID ← 0, -- not always the originator, for named conversations
commentator: Thrush.PartyID ← 0 -- feedback channel party, for named conversations
];
A bilateral conversation is one with two active parties in it, one of which has demanded that the number not exceed two. N.B. This condition will not be set unless both parties are currently in the active state. N.B. This condition will not be set unless the ConversationInfoRec is a component of a PartyInfo returned by GetPartyInfo.
GetConversationInfo: PROC [ shh: SHHH←none, convID: ConversationID ]
RETURNS [
Readily-available (and not too big) information about the conversation
nb: NB, -- one of $success, $noSuchConv
cInfo: ConversationInfo
];
Named conversations
RegisterConversation: PROC [
shhh: SHHH ← none,
credentials: Credentials, -- partyID is owner, convID identifies conversation
name: ROPE, -- Should be unique, but this is not yet enforced; may be some use
convType: ConvType ← $meeting, -- default unregistered is $interactive
convAttributes: Attributes ← NIL, -- Permits changing the subject, etc.
accessList: AccessList ← NIL
] RETURNS [ nb: NB ];
GetConversationFromName: PROC [
shhh: SHHH ← none,
partyID: PartyID,
name: ROPE
] RETURNS [ nb: NB, cInfo: ConversationInfo ];
EnumerateNamedConversations: PROC [
shhh: SHHH ← none,
partyID: PartyID
] RETURNS [ nb: NB, candidates: LIST OF ConversationInfo ];
Party information
PartyInfo: TYPE = REF PartyInfoSeq;
Socket numbers and host names for each of the voice terminals in this conversation. All active parties are arrayed ahead of any others.
nullIx: NAT = 0;
PartyInfoSeq: TYPE = RECORD [
numParties: NAT𡤀,
conversationInfo: ConversationInfoRec, -- description of overall conversation
ixSelf: NAT←nullIx, -- index of own party in parties sequence
ixOther: NAT ← nullIx, -- index of some other (active, if any) party, if any
ixModerator: NAT←nullIx, -- index of moderator party
ixCommentator: NAT←nullIx, -- index of talkBack (commentator) party
ixCommSock: NAT←nullIx, -- index of party whose socket is in use as the commentator socket.
ixOriginator: NAT←nullIx, -- index of originator party in parties sequence (needed?)
parties: SEQUENCE len: NAT OF PartyInfoSpec -- and of individual parties
];
ixSelf value is meaningless if numParties = 0. ixOther value, along with all other indices # ixSelf are meaningless if numParties < 2. ixCommentator, ixCommSock values are meaningless when conversation type is not $meeting.
PartyInfoSpec: TYPE = RECORD [
partyID: PartyID←nullID,
name: Thrush.ROPENIL, -- owner, current name, or description, see below
Call may have been intended for someone else. If not, same as partyID and name
intendedPartyID: PartyID←nullID,
intendedName: Thrush.ROPENIL,
type: Thrush.PartyType←NIL,
state: Thrush.StateInConv←$neverWas, -- of this party in the conversation
numConvs: NAT𡤀,
enabled: BOOL�LSE,
voicePath: BOOLFALSE, -- TRUE iff this party can participate actively in conversations
partyActive: BOOLFALSE, -- actively involved in n-way conversation
partyEngaged: BOOLFALSE, -- hardware in use
socket: Thrush.VoiceSocket←Thrush.noAddress, -- in1 (tx1) socket template this party's voice terminal
partyAttributes: Attributes ← NIL
];
NameReq: TYPE = ATOM; -- { $owner, $current, $description, $address, $none };
$owner: RName of creator of this party; same as $current for telephone and individual parties; RName of trunk's owner for trunks; Idle service RName for services.
$current: Current assignment; identity of party on the other end of a $trunk, or modified service name identifying user.
$address: meaningless but same as $current for all but $trunk, where it's the phone number
$description: dolled-up version of $current, suitable for framing, including all known names and numbers, for Finch and so on.
GetPartyInfo: PROC [
shh: SHHH←none,
credentials: Credentials,
nameReq: NameReq←$current,
allParties: BOOLFALSE -- IF FALSE, just calling party; else all parties, calling party first
]
RETURNS [
Information about the parties (or just one party) in a conversation
Credentials should have either a ConversationID or PartyID or both; other fields irrelevant.
nb: NB, -- one of $success, $noSuchConv, $noSuchParty, $notInConv
pInfo: PartyInfo
];
GetPartyInfo[nameReq: $current, allParties: FALSE] replaces GetRname.
GetPartyInfo[nameReq: $description, allParties: FALSE] replaces DescribeParty
DescribeParty: PROC[partyID: Thrush.PartyID, nameReq: NameReq]
RETURNS[nb: NB, description: Thrush.ROPE, type: Thrush.PartyType,
partner: Thrush.PartyID←nullID, visitee: Thrush.PartyID←nullID,
visitors: LIST OF Thrush.PartyID←NIL
];
partner is poachee if party type is $individual, poacher if party type is $telephone, else NA
visitee and visitors are usually nonexistent, so there's no option for suppressing their values. This should probably just get subsumed into GetPartyInfo.
AddAttributes: PROC[
credentials: Credentials,
convAttributes: Attributes←NIL,
partyAttributes: Attributes←NIL -- own
] RETURNS[nb: NB];
Service action registration, lookup. See also ReportAction, above.
RegisterServiceInterface: PROC[
shhh: SHHH ← none,
credentials: Credentials,
interfaceSpecPattern: Thrush.InterfaceSpec
] RETURNS[nb: NB, interfaceSpec: Thrush.InterfaceSpec];
Having registered it, the caller (service provider) must also XXXRpcControl.ExportInterface[...] the interface, under the specified instance name. interfaceSpecPattern.type should be a simple rope, not a Grapevine RName, since we don't really want Grapevine involved at all. The client can either supply an instance value or let RegisterService invent one (present BluejaySmarts produces 5 parties, but each has the same behavior, so it could use the instance invented during the registration of the first one for the rest of them.)
There can/will be only one instance/version of an interface with a given type name registered for a party at a time. New ones override old ones.
Caller should supply interfaceSpecPattern.hostHint. If it's null, an attempt will be made to produce a decent value, no guarantees (shhh=none implies call is from own host.)
NB can complain about the credentials.
LookupServiceInterface: PROC[
shhh: SHHH ← none,
credentials: Credentials,
serviceParty: PartyID,
type: RPC.ShortROPE
] RETURNS[nb: NB, interfaceSpec: Thrush.InterfaceSpec];
The client can now import the interface directly. Interface procedures should traffic in Thrush.Credentials and the like, providing enough information to identify the relevant conversation and so on. The implementations are responsible for issuing the necessary progress reports, and dealing with the other ThParty rules.
NB is $noSuchInterface if one cannot be found. NB can also complain about caller's credentials or the validity of the serviceParty (see Thrush.NB). LookupService tries the identified serviceParty and its poachee, if there is one, in that order. Version checking is not done by this function; if the versions don't match the Import attempt will discover it.
Encryption key registration.
The voice protocol requires each voice terminal to maintain a table of active encryption keys, whose indices appear in individual voice packets. RegisterKey enters a new key, failing if it would displace an old one. The result is the key's index location.
If reportNewKeys, a Thrush.ActionReport is issued to all but the originator of the request (see ReportAction, above), with actionClass=$KeyDistribution and actionType=$newKeys, uniqueID=(high order half of key). Once all these reports have been delivered, the originator gets one, too. At this point, it is certain that all participants have been given an opportunity to call GetKeyTable and record the new keys.
RegisterKey returns nb=$newKeys if reports are being generated, $keyOverflow if all indices are in use.
Thus ends the infamous and long-standing key synchronization problem.
UnregisterKey permits the use of a key to be released. If everyone plays the game, keyIndices can be re-used during a conversation. UnregisterKey does not inform anyone else of the change. It should not be issued until all parties have stopped using the old key. It is believed that additional synchronization is unnecessary.
RegisterKey: PROC[
shh: SHHH ← none,
credentials: Credentials,
key: Thrush.EncryptionKey,
reportNewKeys: BOOLFALSE
]
RETURNS [ nb: NB, keyIndex: [0..17B] ];
GetKeyTable: PROC [ shh: SHHH←none, credentials: Credentials ]
RETURNS [
nb: NB, -- one of $success, $noSuchConv, $noSuchParty, $notInConv
keyTable: Thrush.KeyTable
];
UnregisterKey: PROC[
shh: SHHH ← none,
credentials: Credentials,
key: Thrush.EncryptionKey
]
RETURNS [ nb: NB ];
Names to Parties, Numbers to Parties
Discussion
Parties represent participants in conversations. Depending on type, they represent:
service: a recording service, synthesizer, and the like.
trunk: the party at the other end of a "back door" telephone line.
telephone: the user of a Lark (Etherphone) in an office.
individual: an authenticated user of a Lark and/or a client program registered with the server.
GetParty and friends map names into parties. (Register creates new parties.) The rName is either the name of an individual (including registry), a service (e.g., "recording"), or (in the case of trunks) NIL.
partyID is the ID for one's own party -- the "self" of object-style references. This form is followed throughout this interface.
GetParty: PROC[
shh: SHHH←none, partyID: PartyID, rName: ROPENIL,
type: Thrush.PartyType←NIL
] RETURNS [nb: NB, newPartyID: PartyID];
IF nb#success, it gives some idea of what went wrong. Thrush.NB can be extended with specialized error values in cases where the caller is expected to understand more about it (when obtaining services, for instance)
IF type=NIL, it's a request for an $individual (preferably) or $telephone (otherwise).
GetParty will return one's own trunk, properly configured, if type=$telephone and no Etherphone party can be found.
GetPartyFromNumber: PROC[
shh: SHHH←none,
partyID: PartyID,
phoneNumber: Thrush.ROPENIL, description: ROPENIL
] RETURNS [nb: NB, newPartyID: PartyID];
phoneNumber is a valid extension or outside telephone number. Description is probably a name, but may not be an RName; we can use it to help the user, but not to look up anything. Unless trunkOK, number must be an internal extension representing an Etherphone user.
GetPartyFromFeepNum: PROC[
shh: SHHH←none,
partyID: PartyID,
feepNum: Thrush.ROPENIL
] RETURNS [nb: NB, newPartyID: PartyID];
FeepNum is a string of the form "*nnnnn", where "nnnnn" is a prefix of some rName, mapped with information loss into the corresponding DTMF buttons. The prefix should be long enough to be guaranteed unique within the system. This isn't at all good yet.
GetCurrentParty: PROC[shh: SHHH←none, smartsID: SmartsID]
RETURNS [nb: NB, partyID: Thrush.PartyID];
Who do we represent when we initiate an outgoing call? After obtaining this party, it may become invalid (due to the party dropping out), so be prepared for $noSuchParty problems all along the line.
ReleaseParty: PROC[shh: SHHH←none, partyID: PartyID, targetPartyID: PartyID]
RETURNS [nb: NB];
Irrelevant except for service party. Optional function to release reservation of targetPartyID. After calling GetParty, a client is expected to engage the targetParty in a conversation fairly quickly, in human terms. The client has the exclusive right to do this for a moderate time interval, after which it's fair game again. This procedure releases that right early.
Information about people and parties
GetNumbersForRName: PROC[shh: SHHH←none, rName: ROPE]
RETURNS [fullRName: ROPE, number: ROPE, homeNumber: ROPE];
fullRName is NIL if nothing found.
Party and Smarts creation, initialization
Discussion
A Smarts implementation uses Register to create the server's identifier for the Smarts, to create the corresponding party, and to establish the necessary links between these objects and the reset of the system. <There used to be some authentication involved in this step, but for now we're trusting RPC to provide authorized identities.>
Since the server needs to be able to deal with more than one Smarts implementation, they are implemented as objects and must use RPC's object-oriented option.
Options include:
Local or remote registration: in the local case, the Smarts implementation is on the same machine and can supply the interface record directly.
Cloning: a service can request additional registrations based on an initial prototype. This allows the service to support a number of simultaneous conversations.
Calling Register with identical party identification results in the immediate destruction (via Deregister) of the previous version of the party and creation of a new one. CheckIn can be used to determine whether such a step is necessary.
Register: PROC[
shh: SHHH←none,
Identification of party -- requires either:
Description of party
rName: ROPENIL,
type: Thrush.PartyType← $individual,
Or ID of party to replicate (used to provide multiple instances of services)
clonePartyID: PartyID ← nullID,
Identification of smarts implementation -- requires either:
RPC Interface allowing an instance to be imported
interface: SmartsInterfaceName,
Or Local Interface record matching that produced by RPC -- see ThPartyPrivate.RegisterLocal
Kind of Smarts being registered, and from what host
properties: SmartsProperties
] RETURNS [
nb: NB, credentials: Credentials ];
nb will indicate something about what went wrong if anything did; if nb=$success,
credentials contains the resulting PartyID and SmartsID.
CheckIn: PROC[shh: SHHH←none, credentials: Credentials] RETURNS [nb: NB];
Verifies that the credentials are still valid, (conv can be null), or if not, which problem is the most pronounced. Can be used as basis for occasional Smarts to party polling.
Deregister: PROC[shh: SHHH←none, smartsID: SmartsID] RETURNS [nb: NB];
nb will be noSuchSmarts or success, or something else (which implies things are confused)
Enable: PROC[shh: SHHH←none, smartsID: SmartsID]
RETURNS [nb: Thrush.NB];
nb=success unless something went wrong. Any parties connected to this smarts will now be available to GetParty; this smarts is an active voice smarts.
Disable: PROC[shh: SHHH←none, smartsID: SmartsID]
RETURNS [nb: Thrush.NB];
This smarts can no longer supply a voice connection to its parties.
noPassword: GVBasics.Password ← ALL[0]; Compiler won't let me define this here; client has to do it in an implementation!
Visit: PROC[
shh: SHHH←none,
hostPartyID: PartyID, guestPartyID: PartyID,
guestPassword: GVBasics.Password
]
RETURNS [nb: Thrush.NB];
visitedParty is permitting visitingParty to visit. Visited party must be of type $individual or this function is not permitted. Returns $passwordNotValid either if visitingPassword = noPassword and password protection is required, or if an invalid password is supplied. A GVBasics.Password may be obtained from VoiceUtils.CurrentPassword (not CurrentPasskey!)
Unvisit: PROC[
shh: SHHH←none,
hostPartyID: PartyID, guestPartyID: PartyID,
guestPassword: GVBasics.Password
]
RETURNS [nb: Thrush.NB];
Visitor has returned home. Password is required, under same circumstances as above, so that no one can maliciously cancel visiting. Visit implementation may choose to retain the key for this reason, although it is not strictly speaking safe to do so.
UnvisitSelf: PROC[
shh: SHHH←none,
guestPartyID: PartyID
]
RETURNS [nb: Thrush.NB];
Allows the visitor (the guest) to cancel the visiting arrangement.
}.
Swinehart, May 15, 1985 10:01:11 am PDT
Cedar 6.0
changes to: GetParty stuff simplified (from client standpoint), Prose functions added, some unused procedures removed.
Swinehart, October 31, 1985 5:18:48 pm PST
Major changes to argument structure, Smarts/Party structure
changes to: Alert, Advance, CreateConversation, GetParty, SetRingEnable, DIRECTORY, LocalSmartsInterface, SmartsProperties, SmartsRole, Register, GetPartyFromFeepNum
Swinehart, November 7, 1985 9:50:41 am PST
Revamping ThParty, (lean, mean Thrush)
changes to: CreateConversation, Alert, Advance
Swinehart, May 30, 1986 10:45:17 am PDT
Cedar 6.1. Add Action Registration, Lookup, Reporting. Clean out some old Interval/Prose stuff.
changes to: DIRECTORY, RegisterServiceInterface
Swinehart, August 3, 1986 6:15:00 pm PDT
Functions to enable and disable "visiting"; UnregisterKey
changes to: GetKeyTable, UnregisterKey, Disable, Visit, Unvisit
Swinehart, September 15, 1987 4:47:36 pm PDT
Changed Visit, Unvisit definitions; added UnvisitSelf
changes to: Visit, Unvisit, UnvisitSelf
Swinehart, January 14, 1988 4:26:26 pm PST
Add named conversation capabilities; reorganize and extend ConversationInfo. Add access lists to conversations (named only at present).
changes to: SmartsRole, AccessList, ConversationInfo, ConversationInfoRec, GetConversationInfo, RegisterConversation, GetConversationFromName, EnumerateNamedConversations, PartyInfo, PartyInfoSeq
Swinehart, February 20, 1988 4:12:02 pm PST
Remove subject, name, callUrgency, alertKind; add partyAttributes, convAttributes to replace all of the above. Add attributes-changing function.
changes to: DIRECTORY, AccessList, Attributes, CreateConversation, Alert, Advance, ConversationInfoRec, RegisterConversation, PartyInfoSpec, DescribeParty